├── .devcontainer └── devcontainer.json ├── .github ├── dependabot.yml └── workflows │ └── pull_request.yml ├── .gitignore ├── .gitmodules ├── .solhint.json ├── .solhintignore ├── Dockerfile ├── LICENSE ├── README.md ├── contracts ├── adapters │ ├── GlacisAbstractAdapter.sol │ ├── GlacisAxelarAdapter.sol │ ├── GlacisCCIPAdapter.sol │ ├── GlacisHyperlaneAdapter.sol │ ├── LayerZero │ │ ├── GlacisLayerZeroAdapter.sol │ │ ├── GlacisLayerZeroV2Adapter.sol │ │ ├── SimpleNonblockingLzApp.sol │ │ └── v2 │ │ │ ├── OAppCoreNoPeer.sol │ │ │ ├── OAppNoPeer.sol │ │ │ └── OAppReceiverNoPeer.sol │ └── Wormhole │ │ ├── GlacisWormholeAdapter.sol │ │ ├── IWormholeReceiver.sol │ │ └── IWormholeRelayer.sol ├── client │ ├── GlacisAccessControlClient.sol │ ├── GlacisClient.sol │ ├── GlacisClientOwnable.sol │ ├── GlacisTokenClient.sol │ └── GlacisTokenClientOwnable.sol ├── commons │ └── GlacisCommons.sol ├── interfaces │ ├── IGlacisAccessControlClient.sol │ ├── IGlacisAdapter.sol │ ├── IGlacisClient.sol │ ├── IGlacisRemoteCounterpartManager.sol │ ├── IGlacisRouter.sol │ ├── IGlacisTokenClient.sol │ ├── IGlacisTokenMediator.sol │ ├── IXERC20.sol │ └── IXERC20Lockbox.sol ├── libraries │ ├── AddressBytes32.sol │ └── AddressString.sol ├── managers │ └── GlacisRemoteCounterpartManager.sol ├── mediators │ ├── GlacisTokenMediator.sol │ └── SimpleTokenMediator.sol ├── routers │ ├── GlacisAbstractRouter.sol │ └── GlacisRouter.sol └── token │ ├── XERC20.sol │ └── XERC20Lockbox.sol ├── foundry.toml ├── package-lock.json ├── package.json ├── prettierrc ├── remappings.txt ├── scripts ├── check-coverage.sh └── gen-coverage.sh └── test ├── GlacisMockSetup.sol ├── LocalTestSetup.sol ├── client ├── abstraction.t.sol ├── accessControl.t.sol ├── adapter.t.sol ├── adapter │ ├── adapter.axelar.t.sol │ ├── adapter.ccip.t.sol │ ├── adapter.hyperlane.t.sol │ └── adapter.lz.t.sol ├── counterparts.sol ├── customAdapter.t.sol ├── ownable.t.sol ├── redundancy.t.sol └── retryManager.t.sol ├── contracts ├── libraries │ └── CheckSum.sol ├── mocks │ ├── axelar │ │ ├── AxelarGasServiceMock.sol │ │ ├── AxelarGatewayMock.sol │ │ ├── AxelarOneWayGatewayMock.sol │ │ └── AxelarRetryGatewayMock.sol │ ├── ccip │ │ └── CCIPRouterMock.sol │ ├── hyperlane │ │ └── HyperlaneMailboxMock.sol │ ├── lz │ │ ├── LayerZeroMock.sol │ │ ├── LayerZeroOneWayMock.sol │ │ └── LayerZeroV2Mock.sol │ └── wormhole │ │ └── WormholeRelayerMock.sol └── samples │ ├── CustomAdapterSample.sol │ ├── GlacisClientSample.sol │ ├── GlacisClientTextSample.sol │ ├── GlacisDAOSample.sol │ ├── GlacisTokenClientSampleDestination.sol │ ├── GlacisTokenClientSampleSource.sol │ ├── control │ ├── AxelarSample.sol │ ├── AxelarTextSample.sol │ ├── CCIPSample.sol │ ├── CCIPTextSample.sol │ ├── HyperlaneSample.sol │ ├── HyperlaneTextSample.sol │ ├── LayerZeroSample.sol │ ├── LayerZeroTextSample.sol │ ├── WormholeSample.sol │ └── WormholeTextSample.sol │ └── token │ ├── BasicXERC20Sample.sol │ ├── ERC20Sample.sol │ ├── XERC20LockboxSample.sol │ ├── XERC20NativeLockboxSample.sol │ └── XERC20Sample.sol ├── lib └── address-string.t.sol ├── router └── router.t.sol └── token ├── customAdapterToken.t.sol ├── mediator.t.sol ├── simpleMediator.t.sol └── token.t.sol /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the 2 | // README at: https://github.com/devcontainers/templates/tree/main/src/docker-existing-dockerfile 3 | { 4 | "name": "Existing Dockerfile", 5 | "build": { 6 | // Sets the run context to one level up instead of the .devcontainer folder. 7 | "context": "..", 8 | // Update the 'dockerFile' property if you aren't using the standard 'Dockerfile' filename. 9 | "dockerfile": "../Dockerfile" 10 | } 11 | 12 | 13 | } 14 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for more information: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | # https://containers.dev/guide/dependabot 6 | 7 | version: 2 8 | updates: 9 | - package-ecosystem: "devcontainers" 10 | directory: "/" 11 | schedule: 12 | interval: weekly 13 | -------------------------------------------------------------------------------- /.github/workflows/pull_request.yml: -------------------------------------------------------------------------------- 1 | name: Glacis Pull Request 2 | run-name: ${{ github.actor }} Glacis Pull Request 3 | on: [pull_request] 4 | jobs: 5 | glacis-tests: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/setup-node@v4 9 | with: 10 | node-version: '18' 11 | 12 | - run: echo "The job was automatically triggered by a ${{ github.event_name }} event." 13 | - run: echo "This job is now running on a ${{ runner.os }} server hosted by GitHub" 14 | - run: echo "The name of the branch is ${{ github.ref }} and the repository is ${{ github.repository }}." 15 | 16 | - name: Check out repository code 17 | uses: actions/checkout@v4 18 | - run: echo "The ${{ github.repository }} repository has been cloned to the runner." 19 | 20 | - run: sudo apt update 21 | - run: sudo apt-get install -y lcov 22 | 23 | - run: rm -f package-lock.json 24 | 25 | - run: npm install 26 | - run: echo "The npm is now ready to test your code on the runner." 27 | 28 | - name: Install Foundry 29 | uses: foundry-rs/foundry-toolchain@v1 30 | 31 | - run: mkdir coverage 32 | - run: npm run coverage 33 | 34 | - name: Enforce requested coverage checks 35 | run: scripts/check-coverage.sh ${{vars.LINE_COVERAGE}} ${{vars.FUNCTION_COVERAGE}} ${{vars.BRANCH_COVERAGE}} 36 | 37 | - run: echo "This job's status is ${{ job.status }}." 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiler files 2 | cache/ 3 | out/ 4 | 5 | # Ignores development broadcast logs 6 | !/broadcast 7 | /broadcast/*/31337/ 8 | /broadcast/**/dry-run/ 9 | 10 | # Docs 11 | docs/ 12 | 13 | # Dotenv file 14 | .env 15 | 16 | node_modules 17 | cache_forge 18 | 19 | .vscode/ 20 | package-lock.json 21 | forge-std 22 | ds-test 23 | 24 | coverage 25 | 26 | lcov.txt 27 | forge-pruned-lcov.info 28 | lcov.html 29 | report -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/forge-std"] 2 | path = lib/forge-std 3 | url = https://github.com/foundry-rs/forge-std 4 | -------------------------------------------------------------------------------- /.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solhint:recommended", 3 | "rules": { 4 | "func-visibility": ["warn",{"ignoreConstructors":true}], 5 | "no-inline-assembly" : "off", 6 | "no-empty-blocks" : "off", 7 | "explicit-types": ["warn","explicit"], 8 | "func-name-mixedcase": "off", 9 | "one-contract-per-file": "off", 10 | "event-name-camelcase": "off", 11 | "check-send-result" : "off", 12 | "var-name-mixedcase": "off", 13 | "no-console" : "off" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.solhintignore: -------------------------------------------------------------------------------- 1 | contracts/adapters/LayerZero/SimpleNonblockingLzApp.sol 2 | test/ -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18 2 | 3 | RUN update-ca-certificates 4 | RUN mkdir foundry &&\ 5 | cd foundry &&\ 6 | curl -L https://foundry.paradigm.xyz | bash &&\ 7 | . ~/.bashrc &&\ 8 | foundryup 9 | RUN apt-get update && apt-get install -y apt-transport-https 10 | RUN apt-get install -y lcov 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # v1-core 2 | Core smart contracts of Glacis v1 3 | 4 | ## Installation 5 | You must install to `node_modules` instead of using git submodules: 6 | 7 | ``` 8 | npm install 9 | ``` 10 | 11 | ## Testing 12 | You can execute forge tests of Glacis core operation through: 13 | 14 | ``` 15 | npm run test 16 | ``` 17 | -------------------------------------------------------------------------------- /contracts/adapters/GlacisAbstractAdapter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity 0.8.18; 4 | 5 | import {IGlacisRouter} from "../interfaces/IGlacisRouter.sol"; 6 | import {IGlacisAdapter} from "../interfaces/IGlacisAdapter.sol"; 7 | import {GlacisCommons} from "../commons/GlacisCommons.sol"; 8 | import {GlacisRemoteCounterpartManager} from "../managers/GlacisRemoteCounterpartManager.sol"; 9 | 10 | error GlacisAbstractAdapter__OnlyGlacisRouterAllowed(); 11 | error GlacisAbstractAdapter__OnlyAdapterAllowed(); 12 | error GlacisAbstractAdapter__DestinationChainIdNotValid(); 13 | error GlacisAbstractAdapter__SourceChainNotRegistered(); 14 | error GlacisAbstractAdapter__IDArraysMustBeSameLength(); 15 | error GlacisAbstractAdapter__NoRemoteAdapterForChainId(uint256 chainId); 16 | error GlacisAbstractAdapter__ChainIsNotAvailable(uint256 toChainId); 17 | 18 | 19 | /// @title Glacis Abstract Adapter for all GMPs 20 | /// @notice All adapters inheriting from this contract will be able to receive GlacisRouter requests through _sendMessage 21 | /// function 22 | abstract contract GlacisAbstractAdapter is 23 | GlacisRemoteCounterpartManager, 24 | IGlacisAdapter 25 | { 26 | IGlacisRouter public immutable GLACIS_ROUTER; 27 | 28 | /// @param _glacisRouter This chain's glacis router 29 | /// @param _owner This adapter's owner 30 | constructor(IGlacisRouter _glacisRouter, address _owner) { 31 | _transferOwnership(_owner); 32 | GLACIS_ROUTER = _glacisRouter; 33 | } 34 | 35 | /// @notice Dispatches payload to specified Glacis chain ID and address through a Glacis Adapter implementation 36 | /// @param toChainId Destination chain (Glacis ID) 37 | /// @param refundAddress The address to refund native asset surplus 38 | /// @param payload Payload to send 39 | function sendMessage( 40 | uint256 toChainId, 41 | address refundAddress, 42 | GlacisCommons.CrossChainGas memory incentives, 43 | bytes memory payload 44 | ) external payable override onlyGlacisRouter { 45 | _sendMessage(toChainId, refundAddress, incentives, payload); 46 | } 47 | 48 | /// @notice Dispatches payload to specified Glacis chain ID and address through a Glacis Adapter implementation 49 | /// @param toChainId Destination chain (Glacis ID) 50 | /// @param refundAddress The address to refund native asset surplus 51 | /// @param payload Payload to send 52 | function _sendMessage( 53 | uint256 toChainId, 54 | address refundAddress, 55 | GlacisCommons.CrossChainGas memory incentives, 56 | bytes memory payload 57 | ) internal virtual; 58 | 59 | /// @dev Verifies that the sender request is always GlacisRouter 60 | modifier onlyGlacisRouter() { 61 | if (msg.sender != address(GLACIS_ROUTER)) 62 | revert GlacisAbstractAdapter__OnlyGlacisRouterAllowed(); 63 | _; 64 | } 65 | 66 | /// @dev Verifies that the source address of the request is an authorized adapter 67 | /// @param sourceAddress Source address 68 | modifier onlyAuthorizedAdapter(uint256 chainId, bytes32 sourceAddress) { 69 | if ( 70 | chainId == 0 || 71 | remoteCounterpart[chainId] == bytes32(0) || 72 | sourceAddress != remoteCounterpart[chainId] 73 | ) { 74 | revert GlacisAbstractAdapter__OnlyAdapterAllowed(); 75 | } 76 | _; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /contracts/adapters/GlacisAxelarAdapter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity 0.8.18; 4 | 5 | import {AxelarExecutable} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/executable/AxelarExecutable.sol"; 6 | import {IAxelarGasService} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGasService.sol"; 7 | import {GlacisAbstractAdapter__IDArraysMustBeSameLength, GlacisAbstractAdapter__DestinationChainIdNotValid, GlacisAbstractAdapter__NoRemoteAdapterForChainId, GlacisAbstractAdapter__ChainIsNotAvailable} from "./GlacisAbstractAdapter.sol"; 8 | import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; 9 | import {AddressString} from "../libraries/AddressString.sol"; 10 | import {GlacisAbstractAdapter} from "./GlacisAbstractAdapter.sol"; 11 | import {IGlacisRouter} from "../routers/GlacisRouter.sol"; 12 | import {AddressBytes32} from "../libraries/AddressBytes32.sol"; 13 | import {GlacisCommons} from "../commons/GlacisCommons.sol"; 14 | 15 | /// @title Glacis Adapter for Axelar 16 | /// @notice A Glacis Adapter for the Axelar network. Sends messages through the Axelar Gateway's callContract() and 17 | /// receives Axelar requests through _execute() 18 | contract GlacisAxelarAdapter is GlacisAbstractAdapter, AxelarExecutable { 19 | using Strings for address; 20 | using AddressString for string; 21 | using AddressBytes32 for bytes32; 22 | using AddressBytes32 for address; 23 | IAxelarGasService public immutable GAS_SERVICE; 24 | 25 | mapping(uint256 => string) internal glacisChainIdToAdapterChainId; 26 | mapping(string => uint256) public adapterChainIdToGlacisChainId; 27 | 28 | event GlacisAxelarAdapter__SetGlacisChainIDs(uint256[] chainIDs, string[] chainLabels); 29 | 30 | /// @param _glacisRouter This chain's glacis router 31 | /// @param _axelarGateway This chain's axelar gateway 32 | /// @param _axelarGasReceiver This chain's axelar gas receiver 33 | /// @param _owner This adapter's owner 34 | constructor( 35 | address _glacisRouter, 36 | address _axelarGateway, 37 | address _axelarGasReceiver, 38 | address _owner 39 | ) 40 | AxelarExecutable(_axelarGateway) 41 | GlacisAbstractAdapter(IGlacisRouter(_glacisRouter), _owner) 42 | { 43 | GAS_SERVICE = IAxelarGasService(_axelarGasReceiver); 44 | } 45 | 46 | /// @notice Sets the corresponding Axelar chain label for the specified Glacis chain ID 47 | /// @param chainIDs Glacis chain IDs 48 | /// @param chainLabels Axelar corresponding chain labels 49 | function setGlacisChainIds( 50 | uint256[] memory chainIDs, 51 | string[] memory chainLabels 52 | ) external onlyOwner { 53 | uint256 chainIdLen = chainIDs.length; 54 | if (chainIdLen != chainLabels.length) 55 | revert GlacisAbstractAdapter__IDArraysMustBeSameLength(); 56 | 57 | for (uint256 i; i < chainIdLen; ) { 58 | uint256 chainId = chainIDs[i]; 59 | string memory chainLabel = chainLabels[i]; 60 | 61 | if (chainId == 0) 62 | revert GlacisAbstractAdapter__DestinationChainIdNotValid(); 63 | 64 | glacisChainIdToAdapterChainId[chainId] = chainLabel; 65 | adapterChainIdToGlacisChainId[chainLabel] = chainId; 66 | 67 | unchecked { 68 | ++i; 69 | } 70 | } 71 | 72 | emit GlacisAxelarAdapter__SetGlacisChainIDs(chainIDs, chainLabels); 73 | } 74 | 75 | /// @notice Gets the corresponding Axelar chain label for the specified Glacis chain ID 76 | /// @param chainId Glacis chain ID 77 | /// @return The corresponding Axelar label 78 | function adapterChainID( 79 | uint256 chainId 80 | ) external view returns (string memory) { 81 | return glacisChainIdToAdapterChainId[chainId]; 82 | } 83 | 84 | /// @notice Queries if the specified Glacis chain ID is supported by this adapter 85 | /// @param chainId Glacis chain ID 86 | /// @return True if chain is supported, false otherwise 87 | function chainIsAvailable( 88 | uint256 chainId 89 | ) public view virtual returns (bool) { 90 | return bytes(glacisChainIdToAdapterChainId[chainId]).length > 0; 91 | } 92 | 93 | /// @notice Dispatches payload to specified Glacis chain ID and address through Axelar GMP 94 | /// @param toChainId Destination chain (Glacis ID) 95 | /// @param refundAddress The address to refund native asset surplus 96 | /// @param payload Payload to send 97 | function _sendMessage( 98 | uint256 toChainId, 99 | address refundAddress, 100 | GlacisCommons.CrossChainGas memory, 101 | bytes memory payload 102 | ) internal override { 103 | string memory destinationChain = glacisChainIdToAdapterChainId[ 104 | toChainId 105 | ]; 106 | if (remoteCounterpart[toChainId] == bytes32(0)) 107 | revert GlacisAbstractAdapter__NoRemoteAdapterForChainId(toChainId); 108 | string memory destinationAddress = remoteCounterpart[toChainId] 109 | .toAddress() 110 | .toHexString(); 111 | if (bytes(destinationChain).length == 0) 112 | revert GlacisAbstractAdapter__ChainIsNotAvailable(toChainId); 113 | 114 | if (msg.value > 0) { 115 | GAS_SERVICE.payNativeGasForContractCall{value: msg.value}( 116 | address(this), 117 | destinationChain, 118 | destinationAddress, 119 | payload, 120 | refundAddress 121 | ); 122 | } 123 | gateway.callContract(destinationChain, destinationAddress, payload); 124 | } 125 | 126 | /// @notice Receives route request from Axelar and routes it to GlacisRouter 127 | /// @param sourceChain Source chain (Axelar chain label) 128 | /// @param sourceAddress Source address on remote chain 129 | /// @param payload Payload to route 130 | function _execute( 131 | string calldata sourceChain, 132 | string calldata sourceAddress, 133 | bytes calldata payload 134 | ) 135 | internal 136 | override 137 | onlyAuthorizedAdapter( 138 | adapterChainIdToGlacisChainId[sourceChain], 139 | _toLowerCase(string(sourceAddress[2:42])).toAddress().toBytes32() 140 | ) 141 | { 142 | GLACIS_ROUTER.receiveMessage(adapterChainIdToGlacisChainId[sourceChain], payload); 143 | } 144 | 145 | /// @notice Converts a string to lowercase 146 | /// @param str The string to convert to lowercase 147 | function _toLowerCase( 148 | string memory str 149 | ) internal pure returns (string memory) { 150 | bytes memory bStr = bytes(str); 151 | bytes memory bLower = new bytes(bStr.length); 152 | uint256 bStrLen = bStr.length; 153 | for (uint256 i; i < bStrLen; ) { 154 | unchecked { 155 | // Uppercase character... 156 | if ((uint8(bStr[i]) >= 65) && (uint8(bStr[i]) <= 90)) { 157 | // So we add 32 to make it lowercase 158 | bLower[i] = bytes1(uint8(bStr[i]) + 32); 159 | } else { 160 | bLower[i] = bStr[i]; 161 | } 162 | ++i; 163 | } 164 | } 165 | return string(bLower); 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /contracts/adapters/GlacisHyperlaneAdapter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity 0.8.18; 4 | 5 | import {GlacisAbstractAdapter} from "./GlacisAbstractAdapter.sol"; 6 | import {IGlacisRouter} from "../routers/GlacisRouter.sol"; 7 | import {IMailbox} from "@hyperlane-xyz/core/contracts/interfaces/IMailbox.sol"; 8 | import {StandardHookMetadata} from "@hyperlane-xyz/core/contracts/hooks/libs/StandardHookMetadata.sol"; 9 | import {TypeCasts} from "@hyperlane-xyz/core/contracts/libs/TypeCasts.sol"; 10 | import {IInterchainSecurityModule} from "@hyperlane-xyz/core/contracts/interfaces/IInterchainSecurityModule.sol"; 11 | import {IPostDispatchHook} from "@hyperlane-xyz/core/contracts/interfaces/hooks/IPostDispatchHook.sol"; 12 | import {GlacisAbstractAdapter__IDArraysMustBeSameLength, GlacisAbstractAdapter__DestinationChainIdNotValid, GlacisAbstractAdapter__NoRemoteAdapterForChainId, GlacisAbstractAdapter__ChainIsNotAvailable} from "./GlacisAbstractAdapter.sol"; 13 | import {GlacisCommons} from "../commons/GlacisCommons.sol"; 14 | 15 | error GlacisHyperlaneAdapter__OnlyMailboxAllowed(); 16 | error GlacisHyperlaneAdapter__FeeNotEnough(); 17 | error GlacisHyperlaneAdapter__RefundAddressMustReceiveNativeCurrency(); 18 | 19 | /// @title Glacis Adapter for Hyperlane 20 | /// @notice A Glacis Adapter for the cannonical Hyperlane network. Sends messages through dispatch() and receives 21 | /// messages via handle() 22 | /// @notice Opted to create our own mailbox client because Hyperlane's base Mailbox refund address was static 23 | contract GlacisHyperlaneAdapter is GlacisAbstractAdapter { 24 | // Required by Hyperlane, if kept as 0 then it will use the default router. 25 | IInterchainSecurityModule public interchainSecurityModule; 26 | IMailbox public immutable MAIL_BOX; 27 | uint32 public immutable LOCAL_DOMAIN; 28 | 29 | uint256 internal constant DEFAULT_GAS_LIMIT = 350_000; 30 | 31 | mapping(uint256 => uint32) public glacisChainIdToAdapterChainId; 32 | mapping(uint32 => uint256) public adapterChainIdToGlacisChainId; 33 | 34 | /// @param _glacisRouter This chain's glacis router 35 | /// @param _hyperlaneMailbox This chain's hyperlane router 36 | /// @param _owner This adapter's owner 37 | constructor( 38 | address _glacisRouter, 39 | address _hyperlaneMailbox, 40 | address _owner 41 | ) GlacisAbstractAdapter(IGlacisRouter(_glacisRouter), _owner) { 42 | MAIL_BOX = IMailbox(_hyperlaneMailbox); 43 | LOCAL_DOMAIN = MAIL_BOX.localDomain(); 44 | } 45 | 46 | /// @notice Sets the corresponding Hyperlane domain for the specified Glacis chain ID 47 | /// @param chainIds Glacis chain IDs 48 | /// @param domains Hyperlane corresponding chain domains 49 | function setGlacisChainIds( 50 | uint256[] memory chainIds, 51 | uint32[] memory domains 52 | ) public onlyOwner { 53 | uint256 chainIdLen = chainIds.length; 54 | if (chainIdLen != domains.length) 55 | revert GlacisAbstractAdapter__IDArraysMustBeSameLength(); 56 | 57 | for (uint256 i; i < chainIdLen; ) { 58 | uint256 chainId = chainIds[i]; 59 | uint32 chainLabel = domains[i]; 60 | 61 | if (chainId == 0) 62 | revert GlacisAbstractAdapter__DestinationChainIdNotValid(); 63 | 64 | glacisChainIdToAdapterChainId[chainId] = chainLabel; 65 | adapterChainIdToGlacisChainId[chainLabel] = chainId; 66 | 67 | unchecked { 68 | ++i; 69 | } 70 | } 71 | } 72 | 73 | /// @notice Gets the corresponding Hyperlane domain ID for the specified Glacis chain ID 74 | /// @param chainId Glacis chain ID 75 | /// @return The corresponding Hyperlane domain ID 76 | function adapterChainID(uint256 chainId) external view returns (uint32) { 77 | return glacisChainIdToAdapterChainId[chainId]; 78 | } 79 | 80 | /// @notice Queries if the specified Glacis chain ID is supported by this adapter 81 | /// @param chainId Glacis chain ID 82 | /// @return True if chain is supported, false otherwise 83 | function chainIsAvailable( 84 | uint256 chainId 85 | ) public view virtual returns (bool) { 86 | return glacisChainIdToAdapterChainId[chainId] != 0; 87 | } 88 | 89 | /// @notice Dispatch payload to specified Glacis chain ID and address through Hyperlane 90 | /// @param toChainId Destination chain (Glacis ID) 91 | /// @param refundAddress The address to send excess fee payments to 92 | /// @param payload Payload to send 93 | function _sendMessage( 94 | uint256 toChainId, 95 | address refundAddress, 96 | GlacisCommons.CrossChainGas memory crossChainGas, 97 | bytes memory payload 98 | ) internal override onlyGlacisRouter { 99 | uint32 destinationDomain = glacisChainIdToAdapterChainId[toChainId]; 100 | bytes32 destinationAddress = remoteCounterpart[toChainId]; 101 | 102 | if (destinationAddress == bytes32(0)) 103 | revert GlacisAbstractAdapter__NoRemoteAdapterForChainId(toChainId); 104 | if (destinationDomain == 0) 105 | revert GlacisAbstractAdapter__ChainIsNotAvailable(toChainId); 106 | 107 | // Generate metadata using refundAddress 108 | uint256 gasLimit = crossChainGas.gasLimit == 0 ? DEFAULT_GAS_LIMIT : crossChainGas.gasLimit; 109 | bytes memory metadata = StandardHookMetadata.formatMetadata({ 110 | _msgValue: 0, // unused by us 111 | _gasLimit: gasLimit, // override default gas limit 112 | _refundAddress: refundAddress, // override default refund address 113 | _customMetadata: "" // none needed for default hook 114 | }); 115 | 116 | // Ensure that we have enough of the required fee (will revert if not this value) 117 | uint256 nativePriceQuote = MAIL_BOX.quoteDispatch( 118 | destinationDomain, 119 | destinationAddress, 120 | payload, 121 | metadata 122 | ); 123 | if (msg.value < nativePriceQuote) { 124 | revert GlacisHyperlaneAdapter__FeeNotEnough(); 125 | } 126 | 127 | // Send message across chains 128 | MAIL_BOX.dispatch{value: nativePriceQuote}( 129 | destinationDomain, 130 | destinationAddress, 131 | payload, 132 | metadata, 133 | IPostDispatchHook(address(0)) // hook 134 | ); 135 | 136 | // Send rest to refund address 137 | if (msg.value > nativePriceQuote) { 138 | (bool successful, ) = address(refundAddress).call{ 139 | value: msg.value - nativePriceQuote 140 | }(""); 141 | if (!successful) 142 | revert GlacisHyperlaneAdapter__RefundAddressMustReceiveNativeCurrency(); 143 | } 144 | } 145 | 146 | /// @notice Receives messages from Hyperlane 147 | /// @param _origin The hyperlane domain ID 148 | /// @param _sender The bytes32 representation of the origin's sender address 149 | /// @param _message The bytes payload of the message 150 | function handle( 151 | uint32 _origin, 152 | bytes32 _sender, 153 | bytes calldata _message 154 | ) 155 | external 156 | payable 157 | onlyAuthorizedAdapter(adapterChainIdToGlacisChainId[_origin], _sender) 158 | { 159 | if (msg.sender != address(MAIL_BOX)) { 160 | revert GlacisHyperlaneAdapter__OnlyMailboxAllowed(); 161 | } 162 | 163 | GLACIS_ROUTER.receiveMessage(adapterChainIdToGlacisChainId[_origin], _message); 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /contracts/adapters/LayerZero/GlacisLayerZeroAdapter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity 0.8.18; 4 | 5 | import {IGlacisRouter} from "../../interfaces/IGlacisRouter.sol"; 6 | import {GlacisAbstractAdapter} from "../GlacisAbstractAdapter.sol"; 7 | import {SimpleNonblockingLzApp} from "./SimpleNonblockingLzApp.sol"; 8 | import {GlacisAbstractAdapter__IDArraysMustBeSameLength, GlacisAbstractAdapter__DestinationChainIdNotValid, GlacisAbstractAdapter__ChainIsNotAvailable, GlacisAbstractAdapter__NoRemoteAdapterForChainId} from "../GlacisAbstractAdapter.sol"; 9 | import {AddressBytes32} from "../../libraries/AddressBytes32.sol"; 10 | import {GlacisCommons} from "../../commons/GlacisCommons.sol"; 11 | 12 | /// @title Glacis Adapter for Layer Zero 13 | /// @notice A Glacis Adapter for the LayerZero V1. Sends messages through _lzSend() and receives 14 | /// messages via _nonblockingLzReceive() 15 | contract GlacisLayerZeroAdapter is 16 | SimpleNonblockingLzApp, 17 | GlacisAbstractAdapter 18 | { 19 | using AddressBytes32 for bytes32; 20 | 21 | constructor( 22 | address _glacisRouter, 23 | address _lzEndpoint, 24 | address _owner 25 | ) 26 | SimpleNonblockingLzApp(_lzEndpoint) 27 | GlacisAbstractAdapter(IGlacisRouter(_glacisRouter), _owner) 28 | {} 29 | 30 | mapping(uint256 => uint16) internal glacisChainIdToAdapterChainId; 31 | mapping(uint16 => uint256) public adapterChainIdToGlacisChainId; 32 | 33 | bytes public adapterParams = bytes(""); 34 | 35 | event GlacisLayerZeroAdapter__SetGlacisChainIDs(uint256[] chainIDs, uint16[] lzIDs); 36 | 37 | /// @notice Sets the corresponding LayerZero chain ID for the specified Glacis chain ID 38 | /// @param chainIDs Glacis chain IDs 39 | /// @param lzIDs Layer Zero chain IDs 40 | function setGlacisChainIds( 41 | uint256[] calldata chainIDs, 42 | uint16[] calldata lzIDs 43 | ) external onlyOwner { 44 | uint256 glacisIDsLen = chainIDs.length; 45 | if (glacisIDsLen != lzIDs.length) 46 | revert GlacisAbstractAdapter__IDArraysMustBeSameLength(); 47 | 48 | for (uint256 i; i < glacisIDsLen; ) { 49 | uint256 glacisID = chainIDs[i]; 50 | uint16 lzID = lzIDs[i]; 51 | 52 | if (glacisID == 0) 53 | revert GlacisAbstractAdapter__DestinationChainIdNotValid(); 54 | 55 | glacisChainIdToAdapterChainId[glacisID] = lzID; 56 | adapterChainIdToGlacisChainId[lzID] = glacisID; 57 | 58 | unchecked { 59 | ++i; 60 | } 61 | } 62 | 63 | emit GlacisLayerZeroAdapter__SetGlacisChainIDs(chainIDs, lzIDs); 64 | } 65 | 66 | /// @notice Gets the corresponding LayerZero chain ID for the specified Glacis chain ID 67 | /// @param chainId Glacis chain ID 68 | /// @return The corresponding LayerZero chain Id as bytes32 69 | function adapterChainID(uint256 chainId) external view returns (uint16) { 70 | return glacisChainIdToAdapterChainId[chainId]; 71 | } 72 | 73 | /// @notice Queries if the specified Glacis chain ID is supported by this adapter 74 | /// @param chainId Glacis chain ID 75 | /// @return True if chain is supported, false otherwise 76 | function chainIsAvailable(uint256 chainId) public view returns (bool) { 77 | return glacisChainIdToAdapterChainId[chainId] != 0; 78 | } 79 | 80 | /// @notice Dispatch payload to specified Glacis chain ID and address through LayerZero GMP 81 | /// @param toChainId Destination chain (Glacis ID) 82 | /// @param refundAddress The address to refund native asset surplus 83 | /// @param payload Payload to send 84 | function _sendMessage( 85 | uint256 toChainId, 86 | address refundAddress, 87 | GlacisCommons.CrossChainGas memory, 88 | bytes memory payload 89 | ) internal override { 90 | bytes32 remoteCounterpart = remoteCounterpart[toChainId]; 91 | uint16 _dstchainId = glacisChainIdToAdapterChainId[toChainId]; 92 | 93 | if (remoteCounterpart == bytes32(0)) 94 | revert GlacisAbstractAdapter__NoRemoteAdapterForChainId(toChainId); 95 | if (_dstchainId == 0) 96 | revert GlacisAbstractAdapter__ChainIsNotAvailable(toChainId); 97 | _lzSend({ 98 | _dstChainId: _dstchainId, 99 | _dstChainAddress: remoteCounterpart.toAddress(), 100 | _payload: payload, 101 | _refundAddress: payable(refundAddress), 102 | _zroPaymentAddress: address(0), 103 | _adapterParams: adapterParams, 104 | _nativeFee: msg.value 105 | }); 106 | } 107 | 108 | /// @notice Receives route message from LayerZero and routes it to GlacisRouter 109 | /// @param srcChainId Source chain (LayerZero ID) 110 | /// @param sourceAddress Source address on remote chain 111 | /// @param payload Payload to route 112 | function _nonblockingLzReceive( 113 | uint16 srcChainId, 114 | bytes memory sourceAddress, // srcAddress, will be the other adapter 115 | uint64, 116 | bytes memory payload 117 | ) 118 | internal 119 | override 120 | // Only supports EVMs 121 | onlyAuthorizedAdapter( 122 | adapterChainIdToGlacisChainId[srcChainId], 123 | bytes32(bytes20(sourceAddress)) >> 96 124 | ) 125 | { 126 | GLACIS_ROUTER.receiveMessage( 127 | adapterChainIdToGlacisChainId[srcChainId], 128 | payload 129 | ); 130 | } 131 | 132 | /// Sets the adapter parameters for LayerZero messages. 133 | /// @param params The desired adapter params. 134 | function setAdapterParams(bytes memory params) external onlyOwner { 135 | adapterParams = params; 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /contracts/adapters/LayerZero/GlacisLayerZeroV2Adapter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.18; 3 | 4 | import {IGlacisRouter} from "../../interfaces/IGlacisRouter.sol"; 5 | import {OAppNoPeer} from "./v2/OAppNoPeer.sol"; 6 | import {Origin} from "@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppReceiver.sol"; 7 | import {MessagingParams} from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol"; 8 | import {OptionsBuilder} from "@layerzerolabs/oapp-evm/contracts/oapp/libs/OptionsBuilder.sol"; 9 | import {GlacisAbstractAdapter} from "../GlacisAbstractAdapter.sol"; 10 | import {AddressBytes32} from "../../libraries/AddressBytes32.sol"; 11 | import {GlacisAbstractAdapter__IDArraysMustBeSameLength, GlacisAbstractAdapter__DestinationChainIdNotValid, GlacisAbstractAdapter__ChainIsNotAvailable, GlacisAbstractAdapter__NoRemoteAdapterForChainId, GlacisAbstractAdapter__OnlyAdapterAllowed} from "../GlacisAbstractAdapter.sol"; 12 | import {GlacisCommons} from "../../commons/GlacisCommons.sol"; 13 | 14 | error GlacisLayerZeroV2Adapter__PeersDisabledUseCounterpartInstead(); 15 | 16 | contract GlacisLayerZeroV2Adapter is OAppNoPeer, GlacisAbstractAdapter { 17 | using AddressBytes32 for bytes32; 18 | using OptionsBuilder for bytes; 19 | 20 | constructor( 21 | address _glacisRouter, 22 | address _lzEndpoint, 23 | address _owner 24 | ) 25 | OAppNoPeer(_lzEndpoint, _owner) 26 | GlacisAbstractAdapter(IGlacisRouter(_glacisRouter), _owner) 27 | {} 28 | 29 | uint128 internal constant DEFAULT_GAS_LIMIT = 350_000; 30 | 31 | mapping(uint256 => uint32) internal glacisChainIdToAdapterChainId; 32 | mapping(uint32 => uint256) public adapterChainIdToGlacisChainId; 33 | 34 | event GlacisLayerZeroV2Adapter__SetGlacisChainIDs( 35 | uint256[] chainIDs, 36 | uint32[] lzIDs 37 | ); 38 | 39 | /// @notice Sets the corresponding LayerZero chain ID for the specified Glacis chain ID 40 | /// @param chainIDs Glacis chain IDs 41 | /// @param lzIDs Layer Zero chain IDs 42 | function setGlacisChainIds( 43 | uint256[] calldata chainIDs, 44 | uint32[] calldata lzIDs 45 | ) external onlyOwner { 46 | uint256 glacisIDsLen = chainIDs.length; 47 | if (glacisIDsLen != lzIDs.length) 48 | revert GlacisAbstractAdapter__IDArraysMustBeSameLength(); 49 | 50 | for (uint256 i; i < glacisIDsLen; ) { 51 | uint256 glacisID = chainIDs[i]; 52 | uint32 lzID = lzIDs[i]; 53 | 54 | if (glacisID == 0) 55 | revert GlacisAbstractAdapter__DestinationChainIdNotValid(); 56 | 57 | glacisChainIdToAdapterChainId[glacisID] = lzID; 58 | adapterChainIdToGlacisChainId[lzID] = glacisID; 59 | 60 | unchecked { 61 | ++i; 62 | } 63 | } 64 | 65 | emit GlacisLayerZeroV2Adapter__SetGlacisChainIDs(chainIDs, lzIDs); 66 | } 67 | 68 | /// @notice Gets the corresponding LayerZero chain ID for the specified Glacis chain ID 69 | /// @param chainId Glacis chain ID 70 | /// @return The corresponding LayerZero chain Id as bytes32 71 | function adapterChainID(uint256 chainId) external view returns (uint32) { 72 | return glacisChainIdToAdapterChainId[chainId]; 73 | } 74 | 75 | /// @notice Queries if the specified Glacis chain ID is supported by this adapter 76 | /// @param chainId Glacis chain ID 77 | /// @return True if chain is supported, false otherwise 78 | function chainIsAvailable(uint256 chainId) public view returns (bool) { 79 | return glacisChainIdToAdapterChainId[chainId] != 0; 80 | } 81 | 82 | /// @notice Dispatch payload to specified Glacis chain ID and address through LayerZero GMP 83 | /// @param toChainId Destination chain (Glacis ID) 84 | /// @param refundAddress The address to refund native asset surplus 85 | /// @param payload Payload to send 86 | function _sendMessage( 87 | uint256 toChainId, 88 | address refundAddress, 89 | GlacisCommons.CrossChainGas memory gas, 90 | bytes memory payload 91 | ) internal override { 92 | bytes32 remoteCounterpart = remoteCounterpart[toChainId]; 93 | uint32 _dstEid = glacisChainIdToAdapterChainId[toChainId]; 94 | 95 | if (remoteCounterpart == bytes32(0)) 96 | revert GlacisAbstractAdapter__NoRemoteAdapterForChainId(toChainId); 97 | if (_dstEid == 0) 98 | revert GlacisAbstractAdapter__ChainIsNotAvailable(toChainId); 99 | 100 | uint128 expectedGasLimit = gas.gasLimit == 0 ? DEFAULT_GAS_LIMIT : gas.gasLimit; 101 | 102 | // solhint-disable-next-line check-send-result 103 | endpoint.send{value: msg.value}( 104 | MessagingParams( 105 | _dstEid, 106 | remoteCounterpart, 107 | payload, 108 | OptionsBuilder.newOptions().addExecutorLzReceiveOption(expectedGasLimit, 0), 109 | false 110 | ), 111 | refundAddress 112 | ); 113 | } 114 | 115 | /** 116 | * @dev Called when data is received from the protocol. It overrides the equivalent function in the parent contract. 117 | * Protocol messages are defined as packets, comprised of the following parameters. 118 | * @param _origin A struct containing information about where the packet came from. 119 | * @param payload Encoded message. 120 | */ 121 | function _lzReceive( 122 | Origin calldata _origin, 123 | bytes32, // guid 124 | bytes calldata payload, 125 | address, // Executor address as specified by the OApp. 126 | bytes calldata // Any extra data or options to trigger on receipt. 127 | ) 128 | internal 129 | override 130 | onlyAuthorizedAdapter( 131 | adapterChainIdToGlacisChainId[_origin.srcEid], 132 | _origin.sender 133 | ) 134 | { 135 | GLACIS_ROUTER.receiveMessage( 136 | adapterChainIdToGlacisChainId[_origin.srcEid], 137 | payload 138 | ); 139 | } 140 | 141 | /** 142 | * @notice Checks if the path initialization is allowed based on the provided origin. 143 | * @param origin The origin information containing the source endpoint and sender address. 144 | * @return Whether the path has been initialized. 145 | * 146 | * @dev This indicates to the endpoint that the OApp has enabled msgs for this particular path to be received. 147 | * @dev This defaults to assuming if a peer has been set, its initialized. 148 | */ 149 | function allowInitializePath( 150 | Origin calldata origin 151 | ) public view override returns (bool) { 152 | return 153 | remoteCounterpart[adapterChainIdToGlacisChainId[origin.srcEid]] == 154 | origin.sender; 155 | } 156 | 157 | function peers(uint32 _eid) external view returns (bytes32 peer) { 158 | return remoteCounterpart[adapterChainIdToGlacisChainId[_eid]]; 159 | } 160 | 161 | function setPeer(uint32, bytes32) external view onlyOwner { 162 | revert GlacisLayerZeroV2Adapter__PeersDisabledUseCounterpartInstead(); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /contracts/adapters/LayerZero/v2/OAppCoreNoPeer.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.18; 4 | 5 | import { Ownable2Step } from "@openzeppelin/contracts/access/Ownable2Step.sol"; 6 | import { IOAppCore, ILayerZeroEndpointV2 } from "@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppCore.sol"; 7 | 8 | /** 9 | * @title OAppCoreNoPeer 10 | * @dev Abstract contract implementing the IOAppCore interface with basic OApp configurations. 11 | */ 12 | abstract contract OAppCoreNoPeer is IOAppCore, Ownable2Step { 13 | // The LayerZero endpoint associated with the given OApp 14 | ILayerZeroEndpointV2 public immutable endpoint; 15 | 16 | /** 17 | * @dev Constructor to initialize the OAppCore with the provided endpoint and delegate. 18 | * @param _endpoint The address of the LOCAL Layer Zero endpoint. 19 | * @param _delegate The delegate capable of making OApp configurations inside of the endpoint. 20 | * 21 | * @dev The delegate typically should be set as the owner of the contract. 22 | */ 23 | constructor(address _endpoint, address _delegate) { 24 | endpoint = ILayerZeroEndpointV2(_endpoint); 25 | 26 | if (_delegate == address(0)) revert InvalidDelegate(); 27 | endpoint.setDelegate(_delegate); 28 | } 29 | 30 | /** 31 | * @notice Sets the delegate address for the OApp. 32 | * @param _delegate The address of the delegate to be set. 33 | * 34 | * @dev Only the owner/admin of the OApp can call this function. 35 | * @dev Provides the ability for a delegate to set configs, on behalf of the OApp, directly on the Endpoint contract. 36 | */ 37 | function setDelegate(address _delegate) public onlyOwner { 38 | endpoint.setDelegate(_delegate); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /contracts/adapters/LayerZero/v2/OAppNoPeer.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.18; 4 | 5 | // @dev Import the 'MessagingFee' and 'MessagingReceipt' so it's exposed to OApp implementers 6 | // solhint-disable-next-line no-unused-import 7 | 8 | // @dev Import the 'Origin' so it's exposed to OApp implementers 9 | // solhint-disable-next-line no-unused-import 10 | import { OAppReceiverNoPeer, Origin } from "./OAppReceiverNoPeer.sol"; 11 | import { OAppCoreNoPeer } from "./OAppCoreNoPeer.sol"; 12 | 13 | /** 14 | * @title OAppNoPeer 15 | * @dev Abstract contract serving as the base for OApp implementation, combining OAppSender and OAppReceiver functionality. 16 | */ 17 | abstract contract OAppNoPeer is OAppReceiverNoPeer { 18 | /** 19 | * @dev Constructor to initialize the OApp with the provided endpoint and owner. 20 | * @param _endpoint The address of the LOCAL LayerZero endpoint. 21 | * @param _delegate The delegate capable of making OApp configurations inside of the endpoint. 22 | */ 23 | constructor(address _endpoint, address _delegate) OAppCoreNoPeer(_endpoint, _delegate) {} 24 | 25 | /** 26 | * @notice Retrieves the OApp version information. 27 | * @return senderVersion The version of the OAppSender.sol implementation. 28 | * @return receiverVersion The version of the OAppReceiver.sol implementation. 29 | */ 30 | function oAppVersion() 31 | public 32 | pure 33 | virtual 34 | override(OAppReceiverNoPeer) 35 | returns (uint64 senderVersion, uint64 receiverVersion) 36 | { 37 | return (0, RECEIVER_VERSION); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /contracts/adapters/LayerZero/v2/OAppReceiverNoPeer.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.18; 4 | 5 | import { IOAppReceiver, Origin } from "@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppReceiver.sol"; 6 | import { OAppCoreNoPeer } from "./OAppCoreNoPeer.sol"; 7 | 8 | /** 9 | * @title OAppReceiverNoPeer 10 | * @dev Abstract contract implementing the ILayerZeroReceiver interface and extending OAppCore for OApp receivers. 11 | */ 12 | abstract contract OAppReceiverNoPeer is IOAppReceiver, OAppCoreNoPeer { 13 | // Custom error message for when the caller is not the registered endpoint/ 14 | error OnlyEndpoint(address addr); 15 | 16 | // @dev The version of the OAppReceiver implementation. 17 | // @dev Version is bumped when changes are made to this contract. 18 | uint64 internal constant RECEIVER_VERSION = 2; 19 | 20 | /** 21 | * @notice Retrieves the OApp version information. 22 | * @return senderVersion The version of the OAppSender.sol contract. 23 | * @return receiverVersion The version of the OAppReceiver.sol contract. 24 | * 25 | * @dev Providing 0 as the default for OAppSender version. Indicates that the OAppSender is not implemented. 26 | * ie. this is a RECEIVE only OApp. 27 | * @dev If the OApp uses both OAppSender and OAppReceiver, then this needs to be override returning the correct versions. 28 | */ 29 | function oAppVersion() public view virtual returns (uint64 senderVersion, uint64 receiverVersion) { 30 | return (0, RECEIVER_VERSION); 31 | } 32 | 33 | /** 34 | * @notice Indicates whether an address is an approved composeMsg sender to the Endpoint. 35 | * @dev _origin The origin information containing the source endpoint and sender address. 36 | * - srcEid: The source chain endpoint ID. 37 | * - sender: The sender address on the src chain. 38 | * - nonce: The nonce of the message. 39 | * @dev _message The lzReceive payload. 40 | * @param _sender The sender address. 41 | * @return isSender Is a valid sender. 42 | * 43 | * @dev Applications can optionally choose to implement separate composeMsg senders that are NOT the bridging layer. 44 | * @dev The default sender IS the OAppReceiver implementer. 45 | */ 46 | function isComposeMsgSender( 47 | Origin calldata /*_origin*/, 48 | bytes calldata /*_message*/, 49 | address _sender 50 | ) public view virtual returns (bool) { 51 | return _sender == address(this); 52 | } 53 | 54 | /** 55 | * @notice Checks if the path initialization is allowed based on the provided origin. 56 | * @return Whether the path has been initialized. 57 | * 58 | * @dev This indicates to the endpoint that the OApp has enabled msgs for this particular path to be received. 59 | * @dev This defaults to assuming if a peer has been set, its initialized. 60 | * Can be overridden by the OApp if there is other logic to determine this. 61 | */ 62 | function allowInitializePath(Origin calldata) public view virtual returns (bool) { 63 | return false; 64 | } 65 | 66 | /** 67 | * @notice Retrieves the next nonce for a given source endpoint and sender address. 68 | * @dev _srcEid The source endpoint ID. 69 | * @dev _sender The sender address. 70 | * @return nonce The next nonce. 71 | * 72 | * @dev The path nonce starts from 1. If 0 is returned it means that there is NO nonce ordered enforcement. 73 | * @dev Is required by the off-chain executor to determine the OApp expects msg execution is ordered. 74 | * @dev This is also enforced by the OApp. 75 | * @dev By default this is NOT enabled. ie. nextNonce is hardcoded to return 0. 76 | */ 77 | function nextNonce(uint32 /*_srcEid*/, bytes32 /*_sender*/) public view virtual returns (uint64 nonce) { 78 | return 0; 79 | } 80 | 81 | /** 82 | * @dev Entry point for receiving messages or packets from the endpoint. 83 | * @param _origin The origin information containing the source endpoint and sender address. 84 | * - srcEid: The source chain endpoint ID. 85 | * - sender: The sender address on the src chain. 86 | * - nonce: The nonce of the message. 87 | * @param _guid The unique identifier for the received LayerZero message. 88 | * @param _message The payload of the received message. 89 | * @param _executor The address of the executor for the received message. 90 | * @param _extraData Additional arbitrary data provided by the corresponding executor. 91 | * 92 | * @dev Entry point for receiving msg/packet from the LayerZero endpoint. 93 | */ 94 | function lzReceive( 95 | Origin calldata _origin, 96 | bytes32 _guid, 97 | bytes calldata _message, 98 | address _executor, 99 | bytes calldata _extraData 100 | ) public payable virtual { 101 | // Ensures that only the endpoint can attempt to lzReceive() messages to this OApp. 102 | if (address(endpoint) != msg.sender) revert OnlyEndpoint(msg.sender); 103 | 104 | // Peer was removed in lieu of GlacisCounterpartManager 105 | 106 | // Call the internal OApp implementation of lzReceive. 107 | _lzReceive(_origin, _guid, _message, _executor, _extraData); 108 | } 109 | 110 | /** 111 | * @dev Internal function to implement lzReceive logic without needing to copy the basic parameter validation. 112 | */ 113 | function _lzReceive( 114 | Origin calldata _origin, 115 | bytes32 _guid, 116 | bytes calldata _message, 117 | address _executor, 118 | bytes calldata _extraData 119 | ) internal virtual; 120 | } 121 | -------------------------------------------------------------------------------- /contracts/adapters/Wormhole/GlacisWormholeAdapter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.18; 3 | 4 | import {IWormholeRelayer} from "./IWormholeRelayer.sol"; 5 | import {IWormholeReceiver} from "./IWormholeReceiver.sol"; 6 | import {GlacisAbstractAdapter} from "../GlacisAbstractAdapter.sol"; 7 | import {IGlacisRouter} from "../../routers/GlacisRouter.sol"; 8 | import {GlacisAbstractAdapter__IDArraysMustBeSameLength, GlacisAbstractAdapter__DestinationChainIdNotValid, GlacisAbstractAdapter__SourceChainNotRegistered, GlacisAbstractAdapter__ChainIsNotAvailable, GlacisAbstractAdapter__NoRemoteAdapterForChainId} from "../GlacisAbstractAdapter.sol"; 9 | import {AddressBytes32} from "../../libraries/AddressBytes32.sol"; 10 | import {GlacisCommons} from "../../commons/GlacisCommons.sol"; 11 | 12 | error GlacisWormholeAdapter__OnlyRelayerAllowed(); 13 | error GlacisWormholeAdapter__AlreadyProcessedVAA(); 14 | error GlacisWormholeAdapter__NotEnoughValueForCrossChainTransaction(); 15 | error GlacisWormholeAdapter__RefundAddressMustReceiveNativeCurrency(); 16 | 17 | /// @title Glacis Adapter for Wormhole 18 | /// @notice A Glacis Adapter for the Wormhole network. Sends messages through the Wormhole Relayer's 19 | /// sendPayloadToEvm() and receives messages via receiveWormholeMessages() 20 | contract GlacisWormholeAdapter is IWormholeReceiver, GlacisAbstractAdapter { 21 | using AddressBytes32 for bytes32; 22 | 23 | IWormholeRelayer public immutable WORMHOLE_RELAYER; 24 | mapping(bytes32 => bool) public seenDeliveryVaaHashes; 25 | 26 | mapping(uint256 => uint16) public glacisChainIdToAdapterChainId; 27 | mapping(uint16 => uint256) public adapterChainIdToGlacisChainId; 28 | 29 | uint256 internal constant GAS_LIMIT = 900000; 30 | uint16 internal immutable WORMHOLE_CHAIN_ID; 31 | uint256 internal constant RECEIVER_VALUE = 0; 32 | 33 | event GlacisWormholeAdapter__SetGlacisChainIDs(uint256[] chainIDs, uint16[] whIDs); 34 | 35 | constructor( 36 | IGlacisRouter _glacisRouter, 37 | address _wormholeRelayer, 38 | uint16 wormholeChainId, 39 | address owner_ 40 | ) GlacisAbstractAdapter(_glacisRouter, owner_) { 41 | WORMHOLE_RELAYER = IWormholeRelayer(_wormholeRelayer); 42 | WORMHOLE_CHAIN_ID = wormholeChainId; 43 | } 44 | 45 | /// @notice Dispatch payload to specified Glacis chain ID and address through Wormhole GMP 46 | /// @param toChainId Destination chain (Glacis ID) 47 | /// @param refundAddress The address to refund native asset surplus 48 | /// @param payload Payload to send 49 | function _sendMessage( 50 | uint256 toChainId, 51 | address refundAddress, 52 | GlacisCommons.CrossChainGas memory incentives, 53 | bytes memory payload 54 | ) internal override { 55 | uint16 _dstchainId = glacisChainIdToAdapterChainId[toChainId]; 56 | bytes32 counterpart = remoteCounterpart[toChainId]; 57 | 58 | if (_dstchainId == 0) 59 | revert GlacisAbstractAdapter__ChainIsNotAvailable(toChainId); 60 | if (counterpart == bytes32(0)) 61 | revert GlacisAbstractAdapter__NoRemoteAdapterForChainId(toChainId); 62 | 63 | uint256 selectedGasLimit = incentives.gasLimit > 0 64 | ? incentives.gasLimit 65 | : GAS_LIMIT; 66 | (uint256 nativePriceQuote, ) = WORMHOLE_RELAYER.quoteEVMDeliveryPrice( 67 | _dstchainId, 68 | RECEIVER_VALUE, 69 | selectedGasLimit 70 | ); 71 | 72 | if (nativePriceQuote > msg.value) 73 | revert GlacisWormholeAdapter__NotEnoughValueForCrossChainTransaction(); 74 | 75 | // Will use the given gas limit, otherwise it will automatically set the 76 | // gas limit to 900k (not recommended) 77 | WORMHOLE_RELAYER.sendPayloadToEvm{value: nativePriceQuote}( 78 | _dstchainId, 79 | counterpart.toAddress(), 80 | payload, 81 | RECEIVER_VALUE, 82 | selectedGasLimit, 83 | WORMHOLE_CHAIN_ID, 84 | refundAddress 85 | ); 86 | 87 | if (msg.value > nativePriceQuote) { 88 | (bool successful, ) = address(refundAddress).call{ 89 | value: msg.value - nativePriceQuote 90 | }(""); 91 | if (!successful) 92 | revert GlacisWormholeAdapter__RefundAddressMustReceiveNativeCurrency(); 93 | } 94 | } 95 | 96 | /// @notice Receives route message from Wormhole and routes it to GlacisRouter 97 | /// @param payload Payload to route 98 | /// @param sourceAddress Source address on remote chain 99 | /// @param sourceChain Source chain (Wormhole ID) 100 | /// @param deliveryHash Wormhole delivery hash 101 | function receiveWormholeMessages( 102 | bytes memory payload, 103 | bytes[] memory, // Not using additional VAAs 104 | bytes32 sourceAddress, 105 | uint16 sourceChain, 106 | bytes32 deliveryHash 107 | ) 108 | external 109 | payable 110 | onlyAuthorizedAdapter( 111 | adapterChainIdToGlacisChainId[sourceChain], 112 | sourceAddress 113 | ) 114 | { 115 | if (msg.sender != address(WORMHOLE_RELAYER)) 116 | revert GlacisWormholeAdapter__OnlyRelayerAllowed(); 117 | if (seenDeliveryVaaHashes[deliveryHash]) 118 | revert GlacisWormholeAdapter__AlreadyProcessedVAA(); 119 | 120 | uint256 sourceChainGlacisId = adapterChainIdToGlacisChainId[ 121 | sourceChain 122 | ]; 123 | if (sourceChainGlacisId == 0) 124 | revert GlacisAbstractAdapter__SourceChainNotRegistered(); 125 | 126 | // Ensure no duplicate deliveries 127 | seenDeliveryVaaHashes[deliveryHash] = true; 128 | 129 | // Forward to the router 130 | GLACIS_ROUTER.receiveMessage(sourceChainGlacisId, payload); 131 | } 132 | 133 | /// @notice Sets the corresponding Wormhole chain label for the specified Glacis chain ID 134 | /// @param chainIDs Glacis chain IDs 135 | /// @param whIDs Wormhole corresponding chain IDs 136 | function setGlacisChainIds( 137 | uint256[] memory chainIDs, 138 | uint16[] memory whIDs 139 | ) external onlyOwner { 140 | uint256 len = chainIDs.length; 141 | if (len != whIDs.length) 142 | revert GlacisAbstractAdapter__IDArraysMustBeSameLength(); 143 | for (uint256 i; i < len; ) { 144 | uint256 gID = chainIDs[i]; 145 | uint16 whID = whIDs[i]; 146 | if (gID == 0) 147 | revert GlacisAbstractAdapter__DestinationChainIdNotValid(); 148 | glacisChainIdToAdapterChainId[gID] = whID; 149 | adapterChainIdToGlacisChainId[whID] = gID; 150 | 151 | unchecked { 152 | ++i; 153 | } 154 | } 155 | 156 | emit GlacisWormholeAdapter__SetGlacisChainIDs(chainIDs, whIDs); 157 | } 158 | 159 | /// @notice Gets the corresponding Wormhole chain ID for the specified Glacis chain ID 160 | /// @param chainId Glacis chain ID 161 | /// @return The corresponding Wormhole chain ID 162 | function adapterChainID(uint256 chainId) external view returns (uint16) { 163 | return glacisChainIdToAdapterChainId[chainId]; 164 | } 165 | 166 | /// @notice Queries if the specified Glacis chain ID is supported by this adapter 167 | /// @param chainId Glacis chain ID 168 | /// @return True if chain is supported, false otherwise 169 | function chainIsAvailable(uint256 chainId) public view returns (bool) { 170 | return glacisChainIdToAdapterChainId[chainId] != 0; 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /contracts/adapters/Wormhole/IWormholeReceiver.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache 2 2 | 3 | pragma solidity 0.8.18; 4 | 5 | /** 6 | * @notice Interface for a contract which can receive Wormhole messages. 7 | */ 8 | interface IWormholeReceiver { 9 | /** 10 | * @notice When a `send` is performed with this contract as the target, this function will be 11 | * invoked by the WormholeRelayer contract 12 | * 13 | * NOTE: This function should be restricted such that only the Wormhole Relayer contract can call it. 14 | * 15 | * We also recommend that this function: 16 | * - Stores all received `deliveryHash`s in a mapping `(bytes32 => bool)`, and 17 | * on every call, checks that deliveryHash has not already been stored in the 18 | * map (This is to prevent other users maliciously trying to relay the same message) 19 | * - Checks that `sourceChain` and `sourceAddress` are indeed who 20 | * you expect to have requested the calling of `send` on the source chain 21 | * 22 | * The invocation of this function corresponding to the `send` request will have msg.value equal 23 | * to the receiverValue specified in the send request. 24 | * 25 | * If the invocation of this function reverts or exceeds the gas limit 26 | * specified by the send requester, this delivery will result in a `ReceiverFailure`. 27 | * 28 | * @param payload - an arbitrary message which was included in the delivery by the 29 | * requester. 30 | * @param additionalVaas - Additional VAAs which were requested to be included in this delivery. 31 | * They are guaranteed to all be included and in the same order as was specified in the 32 | * delivery request. 33 | * @param sourceAddress - the (wormhole format) address on the sending chain which requested 34 | * this delivery. 35 | * @param sourceChain - the wormhole chain ID where this delivery was requested. 36 | * @param deliveryHash - the VAA hash of the deliveryVAA. 37 | * 38 | * NOTE: These signedVaas are NOT verified by the Wormhole core contract prior to being provided 39 | * to this call. Always make sure `parseAndVerify()` is called on the Wormhole core contract 40 | * before trusting the content of a raw VAA, otherwise the VAA may be invalid or malicious. 41 | */ 42 | function receiveWormholeMessages( 43 | bytes memory payload, 44 | bytes[] memory additionalVaas, 45 | bytes32 sourceAddress, 46 | uint16 sourceChain, 47 | bytes32 deliveryHash 48 | ) external payable; 49 | } 50 | -------------------------------------------------------------------------------- /contracts/client/GlacisAccessControlClient.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity 0.8.18; 4 | 5 | import {GlacisCommons} from "../commons/GlacisCommons.sol"; 6 | import {IGlacisAccessControlClient} from "../interfaces/IGlacisAccessControlClient.sol"; 7 | 8 | /// @title Glacis Access Control Client 9 | /// @dev This contract encapsulates Glacis Access Control client logic. Contracts inheriting this will have access to 10 | /// Glacis Access control features 11 | abstract contract GlacisAccessControlClient is GlacisCommons, IGlacisAccessControlClient { 12 | mapping(uint256 => mapping(bytes32 => mapping(address => bool))) public allowedRoutes; 13 | 14 | bytes32 constant internal WILD_BYTES = bytes32(uint256(WILDCARD)); 15 | address constant internal WILD_ADDR = address(WILDCARD); 16 | 17 | /// @notice Adds an allowed route for this client 18 | /// @param route Route to be added 19 | function _addAllowedRoute( 20 | GlacisRoute memory route 21 | ) internal { 22 | allowedRoutes[route.fromChainId][route.fromAddress][route.fromAdapter] = true; 23 | } 24 | 25 | /// @notice Removes an allowed route for this client 26 | /// @param route Allowed route to be removed 27 | function _removeAllowedRoute( 28 | GlacisRoute calldata route 29 | ) internal { 30 | allowedRoutes[route.fromChainId][route.fromAddress][route.fromAdapter] = false; 31 | } 32 | 33 | /// @notice Queries if a route from path GMP+Chain+Address is allowed for this client 34 | /// @param route_ Incoming message route 35 | /// @return True if route is allowed, false otherwise 36 | function isAllowedRoute( 37 | GlacisRoute memory route_, 38 | bytes memory // payload 39 | ) public view override returns (bool) { 40 | return 41 | allowedRoutes[route_.fromChainId][route_.fromAddress][route_.fromAdapter] || 42 | allowedRoutes[WILDCARD][route_.fromAddress][route_.fromAdapter] || 43 | allowedRoutes[WILDCARD][WILD_BYTES][route_.fromAdapter] || 44 | allowedRoutes[route_.fromChainId][WILD_BYTES][route_.fromAdapter] || 45 | (uint160(route_.fromAdapter) <= GLACIS_RESERVED_IDS && ( 46 | allowedRoutes[route_.fromChainId][route_.fromAddress][WILD_ADDR] || 47 | allowedRoutes[route_.fromChainId][WILD_BYTES][WILD_ADDR] || 48 | allowedRoutes[WILDCARD][route_.fromAddress][WILD_ADDR] || 49 | allowedRoutes[WILDCARD][WILD_BYTES][WILD_ADDR] 50 | )); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /contracts/client/GlacisClient.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity 0.8.18; 4 | 5 | import {IGlacisRouter} from "../interfaces/IGlacisRouter.sol"; 6 | import {IGlacisClient} from "../interfaces/IGlacisClient.sol"; 7 | import {GlacisAccessControlClient} from "../client/GlacisAccessControlClient.sol"; 8 | error GlacisClient__CanOnlyBeCalledByRouter(); 9 | error GlacisClient__InvalidRouterAddress(); 10 | 11 | /// @title Glacis Client 12 | /// @dev This contract encapsulates Glacis client side logic, contracts inheriting this will have access to all 13 | /// Glacis features 14 | abstract contract GlacisClient is GlacisAccessControlClient, IGlacisClient { 15 | address public immutable GLACIS_ROUTER; 16 | 17 | event GlacisClient__MessageRouted( 18 | bytes32 indexed messageId, 19 | uint256 toChainId, 20 | bytes32 to 21 | ); 22 | 23 | event GlacisClient__MessageArrived( 24 | address[] fromAdapters, 25 | uint256 fromChainId, 26 | bytes32 fromAddress 27 | ); 28 | 29 | /// @param _glacisRouter This chain's deployment of the GlacisRouter 30 | /// @param _quorum The initial default quorum for this client. If dynamic quorum is to be implemented (depending on payload) 31 | /// this value can be ignored and set to 0 32 | constructor( 33 | address _glacisRouter, 34 | uint256 _quorum 35 | ) GlacisAccessControlClient() IGlacisClient(_quorum) { 36 | if (_glacisRouter == address(0)) 37 | revert GlacisClient__InvalidRouterAddress(); 38 | GLACIS_ROUTER = _glacisRouter; 39 | } 40 | 41 | /// @notice Routes the payload to the specific address on destination chain through GlacisRouter using a single specified GMP 42 | /// @param chainId Destination chain (Glacis chain ID) 43 | /// @param to Destination address on remote chain 44 | /// @param payload Payload to be routed 45 | /// @param adapter Glacis ID of the GMP to be used for the routing 46 | /// @param refundAddress Address to refund excess gas payment 47 | /// @param gasPayment Amount of gas to cover source and destination gas fees (excess will be refunded) 48 | function _routeSingle( 49 | uint256 chainId, 50 | bytes32 to, 51 | bytes memory payload, 52 | address adapter, 53 | address refundAddress, 54 | uint256 gasPayment 55 | ) internal returns (bytes32) { 56 | address[] memory adapters = new address[](1); 57 | adapters[0] = adapter; 58 | CrossChainGas[] memory fees = new CrossChainGas[](1); 59 | fees[0] = CrossChainGas({ 60 | gasLimit: 0, 61 | nativeCurrencyValue: uint128(gasPayment) 62 | }); 63 | (bytes32 messageId,) = IGlacisRouter(GLACIS_ROUTER).route{ 64 | value: gasPayment 65 | }(chainId, to, payload, adapters, fees, refundAddress, false); 66 | emit GlacisClient__MessageRouted(messageId, chainId, to); 67 | return messageId; 68 | } 69 | 70 | /// @notice Routes the payload to the specific address on destination chain through GlacisRouter using 71 | /// specified GMPs. 72 | /// @param chainId Destination chain (Glacis chain ID) 73 | /// @param to Destination address on remote chain 74 | /// @param payload Payload to be routed 75 | /// @param adapters The adapters to use for redundant routing 76 | /// @param fees Payment for each GMP to cover source and destination gas fees (excess will be refunded) 77 | /// @param refundAddress Address to refund excess gas payment 78 | /// @param gasPayment Amount of gas to cover source and destination gas fees (excess will be refunded) 79 | function _routeRedundant( 80 | uint256 chainId, 81 | bytes32 to, 82 | bytes memory payload, 83 | address[] memory adapters, 84 | CrossChainGas[] memory fees, 85 | address refundAddress, 86 | uint256 gasPayment 87 | ) internal returns (bytes32) { 88 | (bytes32 messageId,) = IGlacisRouter(GLACIS_ROUTER).route{ 89 | value: gasPayment 90 | }(chainId, to, payload, adapters, fees, refundAddress, false); 91 | emit GlacisClient__MessageRouted(messageId, chainId, to); 92 | return messageId; 93 | } 94 | 95 | /// @notice Routes the payload to the specific address on destination chain through GlacisRouter using GMPs 96 | /// specified in gmps array 97 | /// @param chainId Destination chain (Glacis chain ID) 98 | /// @param to Destination address on remote chain 99 | /// @param payload Payload to be routed 100 | /// @param adapters An array of custom adapters to be used for the routing 101 | /// @param fees Payment for each GMP to cover source and destination gas fees (excess will be refunded) 102 | /// @param refundAddress Address to refund excess gas payment 103 | /// @param retryable True to enable retry feature for this message 104 | /// @param gasPayment Amount of gas to cover source and destination gas fees (excess will be refunded) 105 | function _route( 106 | uint256 chainId, 107 | bytes32 to, 108 | bytes memory payload, 109 | address[] memory adapters, 110 | CrossChainGas[] memory fees, 111 | address refundAddress, 112 | bool retryable, 113 | uint256 gasPayment 114 | ) internal returns (bytes32,uint256) { 115 | (bytes32 messageId,uint256 nonce) = IGlacisRouter(GLACIS_ROUTER).route{ 116 | value: gasPayment 117 | }(chainId, to, payload, adapters, fees, refundAddress, retryable); 118 | emit GlacisClient__MessageRouted(messageId, chainId, to); 119 | return (messageId,nonce); 120 | } 121 | 122 | /// @notice Routes the payload to the specific address on destination chain through GlacisRouter using GMPs 123 | /// specified in gmps array 124 | /// @param chainId Destination chain (Glacis chain ID) 125 | /// @param to Destination address on remote chain 126 | /// @param payload Payload to be routed 127 | /// @param adapters An array of adapters to be used for the routing 128 | /// @param fees Payment for each GMP to cover source and destination gas fees (excess will be refunded) 129 | /// @param refundAddress Address to refund excess gas payment 130 | /// @param messageId The message ID of the message to retry 131 | /// @param nonce The nonce emitted by the original sent message 132 | /// @param gasPayment Amount of gas to cover source and destination gas fees (excess will be refunded) 133 | function _retryRoute( 134 | uint256 chainId, 135 | bytes32 to, 136 | bytes memory payload, 137 | address[] memory adapters, 138 | CrossChainGas[] memory fees, 139 | address refundAddress, 140 | bytes32 messageId, 141 | uint256 nonce, 142 | uint256 gasPayment 143 | ) internal returns (bytes32) { 144 | IGlacisRouter(GLACIS_ROUTER).routeRetry{value: gasPayment}( 145 | chainId, 146 | to, 147 | payload, 148 | adapters, 149 | fees, 150 | refundAddress, 151 | messageId, 152 | nonce 153 | ); 154 | emit GlacisClient__MessageRouted(messageId, chainId, to); 155 | return messageId; 156 | } 157 | 158 | /// @notice Receives message from GMP(s) through GlacisRouter 159 | /// @param fromAdapters addresses of the adapters sent this message (that reached quorum requirements) 160 | /// @param fromChainId Source chain (Glacis chain ID) 161 | /// @param fromAddress Source address on source chain 162 | /// @param payload Routed payload 163 | function receiveMessage( 164 | address[] memory fromAdapters, 165 | uint256 fromChainId, 166 | bytes32 fromAddress, 167 | bytes memory payload 168 | ) external virtual override { 169 | if (msg.sender != GLACIS_ROUTER) 170 | revert GlacisClient__CanOnlyBeCalledByRouter(); 171 | _receiveMessage(fromAdapters, fromChainId, fromAddress, payload); 172 | emit GlacisClient__MessageArrived(fromAdapters, fromChainId, fromAddress); 173 | } 174 | 175 | /// @notice Receives message from GMP(s) through GlacisRouter 176 | /// @param fromAdapters Adapter addresses 177 | /// @param fromChainId Source chain (Glacis chain ID) 178 | /// @param fromAddress Source address on source chain 179 | /// @param payload Routed payload 180 | function _receiveMessage( 181 | address[] memory fromAdapters, 182 | uint256 fromChainId, 183 | bytes32 fromAddress, 184 | bytes memory payload 185 | ) internal virtual {} 186 | } 187 | -------------------------------------------------------------------------------- /contracts/client/GlacisClientOwnable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity 0.8.18; 4 | 5 | import {GlacisClient} from "./GlacisClient.sol"; 6 | import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; 7 | import {GlacisCommons} from "../commons/GlacisCommons.sol"; 8 | 9 | /// @title Glacis Ownable Client 10 | /// @dev This contract encapsulates Glacis client side logic, contracts inheriting this will have access to all 11 | /// Glacis features 12 | /// @notice This contract is ownable 13 | abstract contract GlacisClientOwnable is GlacisClient, Ownable { 14 | 15 | /// @param _glacisRouter This chain's deployment of the GlacisRouter 16 | /// @param _quorum The default quorum that you would like. If you implement dynamic quorum, this value can be ignored and 17 | /// set to 0 18 | /// @param _owner The owner of this contract 19 | constructor( 20 | address _glacisRouter, 21 | uint256 _quorum, 22 | address _owner 23 | ) GlacisClient(_glacisRouter, _quorum) { 24 | _transferOwnership(_owner); 25 | } 26 | 27 | /// @notice Add an allowed route for this client 28 | /// @param allowedRoute Route to be added 29 | function addAllowedRoute( 30 | GlacisCommons.GlacisRoute memory allowedRoute 31 | ) external onlyOwner { 32 | _addAllowedRoute(allowedRoute); 33 | } 34 | 35 | /// @notice Removes an allowed route for this client 36 | /// @param route Allowed route to be removed 37 | function removeAllowedRoute( 38 | GlacisCommons.GlacisRoute calldata route 39 | ) external onlyOwner { 40 | _removeAllowedRoute(route); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /contracts/client/GlacisTokenClientOwnable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity 0.8.18; 4 | 5 | import {GlacisTokenClient} from "./GlacisTokenClient.sol"; 6 | import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; 7 | import {GlacisCommons} from "../commons/GlacisCommons.sol"; 8 | 9 | /// @title Glacis Ownable Token Client 10 | /// @dev This contract encapsulates Glacis Token Passing client logic, contracts inheriting this will have access to all 11 | /// Glacis Token Passing and Message Passing features 12 | /// @notice This contract is Ownable 13 | abstract contract GlacisTokenClientOwnable is GlacisTokenClient, Ownable { 14 | constructor( 15 | address glacisTokenMediator_, 16 | address glacisRouter_, 17 | uint256 quorum, 18 | address owner_ 19 | ) GlacisTokenClient(glacisTokenMediator_, glacisRouter_, quorum) { 20 | _transferOwnership(owner_); 21 | } 22 | 23 | /// @notice Add an allowed route for this client 24 | /// @param allowedRoute Route to be added 25 | function addAllowedRoute( 26 | GlacisCommons.GlacisRoute memory allowedRoute 27 | ) external onlyOwner { 28 | _addAllowedRoute(allowedRoute); 29 | } 30 | 31 | /// @notice Removes an allowed route for this client 32 | /// @param route Allowed route to be removed 33 | function removeAllowedRoute( 34 | GlacisCommons.GlacisRoute calldata route 35 | ) external onlyOwner { 36 | _removeAllowedRoute(route); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /contracts/commons/GlacisCommons.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity 0.8.18; 4 | 5 | /// @title Glacis Commons 6 | /// @dev Contract for utility functions and structures common to Glacis Client and Infrastructure 7 | contract GlacisCommons { 8 | struct GlacisData { 9 | bytes32 messageId; 10 | uint256 nonce; 11 | bytes32 originalFrom; 12 | bytes32 originalTo; 13 | } 14 | 15 | struct GlacisTokenData { 16 | address glacisToken; 17 | uint256 glacisTokenAmount; 18 | } 19 | 20 | struct GlacisRoute { 21 | uint256 fromChainId; // WILDCARD means any chain 22 | bytes32 fromAddress; // WILDCARD means any address 23 | address fromAdapter; // WILDCARD means any GMP, can also hold address 24 | } 25 | 26 | struct CrossChainGas { 27 | uint128 gasLimit; 28 | uint128 nativeCurrencyValue; 29 | } 30 | 31 | uint160 constant public WILDCARD = type(uint160).max; 32 | uint256 constant public GLACIS_RESERVED_IDS = 248; 33 | } 34 | -------------------------------------------------------------------------------- /contracts/interfaces/IGlacisAccessControlClient.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | import {GlacisCommons} from "../commons/GlacisCommons.sol"; 4 | 5 | /// @title IGlacisAccessControlClient 6 | /// @notice An interface that determines Glacis' required access control 7 | interface IGlacisAccessControlClient { 8 | /// @notice Adds an allowed route for this client 9 | /// @notice Queries if a route from path GMP+Chain+Address is allowed for this client 10 | /// @param route The origin route for the message 11 | /// @param payload The payload of a message 12 | /// @return True if route is allowed, false otherwise 13 | function isAllowedRoute( 14 | GlacisCommons.GlacisRoute memory route, 15 | bytes memory payload 16 | ) external view returns (bool); 17 | } 18 | -------------------------------------------------------------------------------- /contracts/interfaces/IGlacisAdapter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | import {GlacisCommons} from "../commons/GlacisCommons.sol"; 5 | 6 | /// @title IGlacisAdapter 7 | /// @notice An interface that defines the GMP modules (adapters) that the GlacisRouter interacts with. 8 | interface IGlacisAdapter { 9 | /// Determines If a chain is available through this adapter 10 | /// @param chainId The Glacis chain ID 11 | /// @return True if the chain is available, false otherwise 12 | function chainIsAvailable(uint256 chainId) external view returns (bool); 13 | 14 | /// Sends a payload across chains to the destination router. 15 | /// @param chainId The Glacis chain ID 16 | /// @param refundAddress The address to refund excessive gas payment to 17 | /// @param payload The data packet to send across chains 18 | function sendMessage( 19 | uint256 chainId, 20 | address refundAddress, 21 | GlacisCommons.CrossChainGas memory incentives, 22 | bytes memory payload 23 | ) external payable; 24 | } 25 | -------------------------------------------------------------------------------- /contracts/interfaces/IGlacisClient.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | import {GlacisCommons} from "../commons/GlacisCommons.sol"; 5 | import {IGlacisAccessControlClient} from "../interfaces/IGlacisAccessControlClient.sol"; 6 | 7 | /// @title IGlacisClient 8 | /// @notice An interface that defines the GMP modules (adapters) that the GlacisRouter interacts with. 9 | abstract contract IGlacisClient is IGlacisAccessControlClient { 10 | uint256 private immutable DEFAULT_QUORUM; 11 | 12 | /// @param _defaultQuorum The default quorum that you would like. If you implement dynamic quorum, this value can be ignored 13 | /// and set to 0 14 | constructor(uint256 _defaultQuorum) { 15 | DEFAULT_QUORUM = _defaultQuorum; 16 | } 17 | 18 | /// @notice Receives message from GMP(s) through GlacisRouter 19 | /// @param fromAdapters Used adapters that sent this message (that reached quorum requirements) 20 | /// @param fromChainId Source chain (Glacis chain ID) 21 | /// @param fromAddress Source address on source chain 22 | /// @param payload Routed payload 23 | function receiveMessage( 24 | address[] calldata fromAdapters, 25 | uint256 fromChainId, 26 | bytes32 fromAddress, 27 | bytes calldata payload 28 | ) external virtual; 29 | 30 | /// @notice The quorum of messages that the contract expects with a specific message 31 | function getQuorum( 32 | GlacisCommons.GlacisData memory, // glacis data 33 | bytes memory, // payload 34 | uint256 // unique messages received so far (for dynamic quorum, usually unused) 35 | ) public view virtual returns (uint256) { 36 | return DEFAULT_QUORUM; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /contracts/interfaces/IGlacisRemoteCounterpartManager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity 0.8.18; 4 | 5 | /// @title IGlacisRemoteCounterpartManager 6 | /// @notice An interface that defines the existence and addition of a contract's remote counterparts 7 | interface IGlacisRemoteCounterpartManager { 8 | /// @notice Adds an authorized glacis counterpart component in a remote chain that interacts with this component 9 | /// @param chainIDs An array with chains of the glacis remote components 10 | /// @param glacisComponents An array of addresses of the glacis components on remote chains 11 | function addRemoteCounterparts( 12 | uint256[] calldata chainIDs, 13 | bytes32[] calldata glacisComponents 14 | ) external; 15 | 16 | /// @notice Removes an authorized glacis counterpart component on remote chain that this components interacts with 17 | /// @param chainId The chainId to remove the remote component 18 | function removeRemoteCounterpart(uint256 chainId) external; 19 | 20 | /// @notice Gets an authorized glacis counterpart component on remote chain that this components interacts with 21 | /// @param chainId The chainId to of the remote component 22 | function getRemoteCounterpart(uint256 chainId) external returns (bytes32); 23 | } 24 | -------------------------------------------------------------------------------- /contracts/interfaces/IGlacisRouter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity 0.8.18; 4 | 5 | import {GlacisCommons} from "../commons/GlacisCommons.sol"; 6 | 7 | /// @title IGlacisRouterEvents 8 | /// @notice An interface that defines a GlacisRouter's events 9 | abstract contract IGlacisRouterEvents is GlacisCommons 10 | { 11 | event GlacisAbstractRouter__MessageIdCreated( 12 | bytes32 indexed messageId, 13 | bytes32 indexed sender, 14 | uint256 nonce 15 | ); 16 | event GlacisAbstractRouter__AdapterRegistered( 17 | uint8 indexed gmpId, 18 | address indexed adapterAddress, 19 | address indexed previousAddress 20 | ); 21 | event GlacisAbstractRouter__AdapterUnregistered( 22 | uint8 indexed gmpId, 23 | address indexed adapterAddress 24 | ); 25 | event GlacisRouter__ReceivedMessage( 26 | bytes32 indexed messageId, 27 | bytes32 indexed from, 28 | uint256 indexed fromChainId, 29 | address adapter, 30 | bytes32 to 31 | ); 32 | event GlacisRouter__ExecutedMessage( 33 | bytes32 indexed messageId, 34 | bytes32 indexed from, 35 | uint256 indexed fromChainId, 36 | address adapter, 37 | bytes32 to 38 | ); 39 | event GlacisRouter__MessageDispatched( 40 | bytes32 indexed messageId, 41 | bytes32 indexed from, 42 | uint256 indexed toChainId, 43 | bytes32 to, 44 | bytes data, 45 | address[] adapters, 46 | CrossChainGas[] fees, 47 | address refundAddress, 48 | bool retryable 49 | ); 50 | event GlacisRouter__MessageRetried( 51 | bytes32 indexed messageId, 52 | bytes32 indexed from, 53 | uint256 indexed toChainId, 54 | bytes32 to, 55 | bytes data, 56 | address[] adapters, 57 | CrossChainGas[] fees, 58 | address refundAddress 59 | ); 60 | } 61 | 62 | /// @title IGlacisRouter 63 | /// @notice An interface that defines an interface that sends and receives messages across chains 64 | interface IGlacisRouter { 65 | /// @notice Routes the payload to the specific address on the destination chain 66 | /// using specified adapters 67 | /// @param chainId Destination chain (EIP-155) 68 | /// @param to Destination address on remote chain 69 | /// @param payload Payload to be routed 70 | /// @param adapters An array of adapters to be used for the routing (addresses 0x01-0xF8 for Glacis adapters 71 | /// or specific addresses for custom adapters) 72 | /// @param fees Array of fees to be sent to each GMP & custom adapter for routing (must be same length as gmps) 73 | /// @param refundAddress An address for native currency to be sent to that are greater than fees charged. If it is a 74 | /// contract it needs to support receive function, reverted otherwise 75 | /// @param retryable True if this message could pottentially be retried 76 | /// @return A tuple with a bytes32 messageId and a uint256 nonce 77 | function route( 78 | uint256 chainId, 79 | bytes32 to, 80 | bytes memory payload, 81 | address[] memory adapters, 82 | GlacisCommons.CrossChainGas[] memory fees, 83 | address refundAddress, 84 | bool retryable 85 | ) external payable returns (bytes32, uint256); 86 | 87 | /// @notice Retries routing the payload to the specific address on destination chain 88 | /// using specified GMPs and quorum 89 | /// @param chainId Destination chain (EIP-155) 90 | /// @param to Destination address on remote chain 91 | /// @param payload Payload to be routed 92 | /// @param adapters An array of adapters to be used for the routing (addresses 0x01-0xF8 for Glacis adapters 93 | /// or specific addresses for custom adapters) 94 | /// @param fees Array of fees to be sent to each GMP & custom adapter for routing (must be same length as gmps) 95 | /// @param refundAddress An address for native currency to be sent to that are greater than fees charged. If it is a 96 | /// contract it needs to support receive function, tx will revert otherwise 97 | /// @param messageId The messageId to retry 98 | /// @param nonce Unique value for this message routing 99 | /// @return A tuple with a bytes32 messageId and a uint256 nonce 100 | function routeRetry( 101 | uint256 chainId, 102 | bytes32 to, 103 | bytes memory payload, 104 | address[] memory adapters, 105 | GlacisCommons.CrossChainGas[] memory fees, 106 | address refundAddress, 107 | bytes32 messageId, 108 | uint256 nonce 109 | ) external payable returns (bytes32, uint256); 110 | 111 | /// @notice Receives a cross chain message from an IGlacisAdapter. 112 | /// @param fromChainId Source chain (EIP-155) 113 | /// @param glacisPayload Received payload with embedded GlacisData 114 | function receiveMessage( 115 | uint256 fromChainId, 116 | bytes memory glacisPayload 117 | ) external; 118 | } 119 | -------------------------------------------------------------------------------- /contracts/interfaces/IGlacisTokenClient.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | import {GlacisCommons} from "../commons/GlacisCommons.sol"; 5 | import {IGlacisAccessControlClient} from "./IGlacisAccessControlClient.sol"; 6 | 7 | /// @title IGlacisTokenClient 8 | /// @notice An interface that defines the GMP modules (adapters) that the GlacisRouter interacts with. 9 | /// @notice Should be paired with the IGlacisClient abstract smart contract. 10 | interface IGlacisTokenClient is IGlacisAccessControlClient { 11 | /// @notice Receives message from GMP(s) through GlacisRouter 12 | /// @param fromAdapters Addresses of the adapters that sent this message (that reached quorum requirements) 13 | /// @param fromChainId Source chain (Glacis chain ID) 14 | /// @param fromAddress Source address on source chain 15 | /// @param payload Routed payload 16 | function receiveMessageWithTokens( 17 | address[] memory fromAdapters, 18 | uint256 fromChainId, 19 | bytes32 fromAddress, 20 | bytes calldata payload, 21 | address token, 22 | uint256 amount 23 | ) external; 24 | 25 | /// @notice The quorum of messages that the contract expects with a specific message from the 26 | /// token router 27 | function getQuorum( 28 | GlacisCommons.GlacisData memory, 29 | bytes memory, 30 | uint256, 31 | address, 32 | uint256 33 | ) external view returns (uint256); 34 | } 35 | -------------------------------------------------------------------------------- /contracts/interfaces/IGlacisTokenMediator.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity 0.8.18; 4 | 5 | import {GlacisCommons} from "../commons/GlacisCommons.sol"; 6 | 7 | /// @title IGlacisTokenMediator 8 | /// @notice An interface of a mediator that sends XERC20s with a payload across chains 9 | interface IGlacisTokenMediator { 10 | event GlacisTokenMediator__TokensBurnt( 11 | address indexed from, 12 | address indexed token, 13 | uint256 amount 14 | ); 15 | event GlacisTokenMediator__TokensMinted( 16 | address indexed to, 17 | address indexed token, 18 | uint256 amount 19 | ); 20 | 21 | /// @notice Routes the payload to the specific address on destination chain through GlacisRouter using GMPs 22 | /// specified in gmps array 23 | /// @param chainId Destination chain (Glacis chain ID) 24 | /// @param to Destination address on remote chain 25 | /// @param payload Payload to be routed 26 | /// @param adapters An array of custom adapters to be used for the routing 27 | /// @param fees Payment for each GMP & custom adapter to cover source and destination gas fees (excess will be refunded) 28 | /// @param refundAddress Address to refund excess gas payment 29 | /// @param token Token (implementing XERC20 standard) to be sent to remote contract 30 | /// @param tokenAmount Amount of token to send to remote contract 31 | /// @return A tuple with a bytes32 messageId and a uint256 nonce 32 | function route( 33 | uint256 chainId, 34 | bytes32 to, 35 | bytes memory payload, 36 | address[] memory adapters, 37 | GlacisCommons.CrossChainGas[] memory fees, 38 | address refundAddress, 39 | address token, 40 | uint256 tokenAmount 41 | ) external payable returns (bytes32, uint256); 42 | 43 | /// @notice Retries routing the payload to the specific address on destination chain using specified GMPs 44 | /// @param chainId Destination chain (Glacis chain ID) 45 | /// @param to Destination address on remote chain 46 | /// @param payload Payload to be routed 47 | /// @param adapters An array of custom adapters to be used for the routing 48 | /// @param fees Payment for each GMP to cover source and destination gas fees (excess will be refunded) 49 | /// @param refundAddress Address to refund excess gas payment 50 | /// @param messageId The message ID of the message to retry 51 | /// @param nonce The nonce emitted by the original message routing 52 | /// @param token Token (implementing XERC20 standard) to be sent to remote contract 53 | /// @param tokenAmount Amount of token to send to remote contract 54 | /// @return A tuple with a bytes32 messageId and a uint256 nonce 55 | function routeRetry( 56 | uint256 chainId, 57 | bytes32 to, 58 | bytes memory payload, 59 | address[] memory adapters, 60 | GlacisCommons.CrossChainGas[] memory fees, 61 | address refundAddress, 62 | bytes32 messageId, 63 | uint256 nonce, 64 | address token, 65 | uint256 tokenAmount 66 | ) external payable returns (bytes32, uint256); 67 | } 68 | -------------------------------------------------------------------------------- /contracts/interfaces/IXERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.18; 3 | 4 | interface IXERC20 { 5 | /** 6 | * @notice Emits when a lockbox is set 7 | * 8 | * @param _lockbox The address of the lockbox 9 | */ 10 | 11 | event LockboxSet(address _lockbox); 12 | 13 | /** 14 | * @notice Emits when a limit is set 15 | * 16 | * @param _mintingLimit The updated minting limit we are setting to the bridge 17 | * @param _burningLimit The updated burning limit we are setting to the bridge 18 | * @param _bridge The address of the bridge we are setting the limit too 19 | */ 20 | event BridgeLimitsSet( 21 | uint256 _mintingLimit, 22 | uint256 _burningLimit, 23 | address indexed _bridge 24 | ); 25 | 26 | /** 27 | * @notice Reverts when a user with too low of a limit tries to call mint/burn 28 | */ 29 | 30 | error IXERC20_NotHighEnoughLimits(); 31 | 32 | /** 33 | * @notice Reverts when caller is not the factory 34 | */ 35 | error IXERC20_NotFactory(); 36 | 37 | struct Bridge { 38 | BridgeParameters minterParams; 39 | BridgeParameters burnerParams; 40 | } 41 | 42 | struct BridgeParameters { 43 | uint256 timestamp; 44 | uint256 ratePerSecond; 45 | uint256 maxLimit; 46 | uint256 currentLimit; 47 | } 48 | 49 | /** 50 | * @notice Sets the lockbox address 51 | * 52 | * @param _lockbox The address of the lockbox 53 | */ 54 | 55 | function setLockbox(address _lockbox) external; 56 | 57 | /** 58 | * @notice Updates the limits of any bridge 59 | * @dev Can only be called by the owner 60 | * @param _mintingLimit The updated minting limit we are setting to the bridge 61 | * @param _burningLimit The updated burning limit we are setting to the bridge 62 | * @param _bridge The address of the bridge we are setting the limits too 63 | */ 64 | function setLimits( 65 | address _bridge, 66 | uint256 _mintingLimit, 67 | uint256 _burningLimit 68 | ) external; 69 | 70 | /** 71 | * @notice Returns the max limit of a minter 72 | * 73 | * @param _minter The minter we are viewing the limits of 74 | * @return _limit The limit the minter has 75 | */ 76 | function mintingMaxLimitOf( 77 | address _minter 78 | ) external view returns (uint256 _limit); 79 | 80 | /** 81 | * @notice Returns the max limit of a bridge 82 | * 83 | * @param _bridge the bridge we are viewing the limits of 84 | * @return _limit The limit the bridge has 85 | */ 86 | 87 | function burningMaxLimitOf( 88 | address _bridge 89 | ) external view returns (uint256 _limit); 90 | 91 | /** 92 | * @notice Returns the current limit of a minter 93 | * 94 | * @param _minter The minter we are viewing the limits of 95 | * @return _limit The limit the minter has 96 | */ 97 | 98 | function mintingCurrentLimitOf( 99 | address _minter 100 | ) external view returns (uint256 _limit); 101 | 102 | /** 103 | * @notice Returns the current limit of a bridge 104 | * 105 | * @param _bridge the bridge we are viewing the limits of 106 | * @return _limit The limit the bridge has 107 | */ 108 | 109 | function burningCurrentLimitOf( 110 | address _bridge 111 | ) external view returns (uint256 _limit); 112 | 113 | /** 114 | * @notice Mints tokens for a user 115 | * @dev Can only be called by a minter 116 | * @param _user The address of the user who needs tokens minted 117 | * @param _amount The amount of tokens being minted 118 | */ 119 | 120 | function mint(address _user, uint256 _amount) external; 121 | 122 | /** 123 | * @notice Burns tokens for a user 124 | * @dev Can only be called by a minter 125 | * @param _user The address of the user who needs tokens burned 126 | * @param _amount The amount of tokens being burned 127 | */ 128 | 129 | function burn(address _user, uint256 _amount) external; 130 | } 131 | 132 | /** 133 | * An optional extension to IXERC20 that the GlacisTokenMediator will query for. 134 | * It allows developers to have XERC20 tokens that have different addresses on 135 | * different chains. 136 | */ 137 | interface IXERC20GlacisExtension { 138 | /** 139 | * @notice Returns a token variant for a specific chainId if it exists. 140 | * 141 | * @param chainId The chainId of the token variant. 142 | */ 143 | function getTokenVariant(uint256 chainId) external view returns (bytes32); 144 | 145 | /** 146 | * @notice Sets a token variant for a specific chainId. 147 | * 148 | * @param chainId The chainId of the token variant. 149 | * @param variant The address of the token variant. 150 | */ 151 | function setTokenVariant(uint256 chainId, bytes32 variant) external; 152 | } 153 | -------------------------------------------------------------------------------- /contracts/interfaces/IXERC20Lockbox.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.18; 3 | 4 | interface IXERC20Lockbox { 5 | /** 6 | * @notice Emitted when tokens are deposited into the lockbox 7 | */ 8 | 9 | event Deposit(address _sender, uint256 _amount); 10 | 11 | /** 12 | * @notice Emitted when tokens are withdrawn from the lockbox 13 | */ 14 | 15 | event Withdraw(address _sender, uint256 _amount); 16 | 17 | /** 18 | * @notice Reverts when a user tries to deposit native tokens on a non-native lockbox 19 | */ 20 | 21 | error IXERC20Lockbox_NotNative(); 22 | 23 | /** 24 | * @notice Reverts when a user tries to deposit non-native tokens on a native lockbox 25 | */ 26 | 27 | error IXERC20Lockbox_Native(); 28 | 29 | /** 30 | * @notice Reverts when a user tries to withdraw and the call fails 31 | */ 32 | 33 | error IXERC20Lockbox_WithdrawFailed(); 34 | 35 | /** 36 | * @notice Deposit ERC20 tokens into the lockbox 37 | * 38 | * @param _amount The amount of tokens to deposit 39 | */ 40 | 41 | function deposit(uint256 _amount) external; 42 | 43 | /** 44 | * @notice Deposit ERC20 tokens into the lockbox, and send the XERC20 to a user 45 | * 46 | * @param _user The user to send the XERC20 to 47 | * @param _amount The amount of tokens to deposit 48 | */ 49 | 50 | function depositTo(address _user, uint256 _amount) external; 51 | 52 | /** 53 | * @notice Deposit the native asset into the lockbox, and send the XERC20 to a user 54 | * 55 | * @param _user The user to send the XERC20 to 56 | */ 57 | 58 | function depositNativeTo(address _user) external payable; 59 | 60 | /** 61 | * @notice Withdraw ERC20 tokens from the lockbox 62 | * 63 | * @param _amount The amount of tokens to withdraw 64 | */ 65 | 66 | function withdraw(uint256 _amount) external; 67 | 68 | /** 69 | * @notice Withdraw ERC20 tokens from the lockbox 70 | * 71 | * @param _user The user to withdraw to 72 | * @param _amount The amount of tokens to withdraw 73 | */ 74 | 75 | function withdrawTo(address _user, uint256 _amount) external; 76 | } 77 | -------------------------------------------------------------------------------- /contracts/libraries/AddressBytes32.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity 0.8.18; 4 | 5 | /// @title Address to Bytes32 Library 6 | /// @notice A library that converts address to bytes32 and bytes32 to address 7 | library AddressBytes32 { 8 | /// @notice Converts an address to bytes32 9 | /// @param addr The address to be converted 10 | function toBytes32(address addr) internal pure returns (bytes32) { 11 | return bytes32(uint256(uint160(addr))); 12 | } 13 | 14 | function toAddress(bytes32 b) internal pure returns (address) { 15 | return address(uint160(uint256(b))); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /contracts/libraries/AddressString.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity 0.8.18; 4 | 5 | /// @title String to Address Library 6 | /// @notice A library that a string to addresses 7 | library AddressString { 8 | error AddressString__InvalidByteValue(bytes1 b); 9 | 10 | /// @notice Converts an hex string to address 11 | /// @param _hexString The hex string to be converted 12 | function toAddress( 13 | string memory _hexString 14 | ) internal pure returns (address) { 15 | bytes memory byte_sString = bytes(_hexString); 16 | uint160 _parsedBytes = 0; 17 | for (uint256 i = 0; i < byte_sString.length; i += 2) { 18 | _parsedBytes *= 256; 19 | uint8 byte_Value = parseByteToUint8(byte_sString[i]); 20 | byte_Value *= 16; 21 | byte_Value += parseByteToUint8(byte_sString[i + 1]); 22 | _parsedBytes += byte_Value; 23 | } 24 | return address(bytes20(_parsedBytes)); 25 | } 26 | 27 | /// @notice Converts a bytes1 to uint8 28 | /// @param _byte The byte value to convert 29 | function parseByteToUint8(bytes1 _byte) internal pure returns (uint8) { 30 | if (uint8(_byte) >= 48 && uint8(_byte) <= 57) { 31 | return uint8(_byte) - 48; 32 | } else if (uint8(_byte) >= 65 && uint8(_byte) <= 70) { 33 | return uint8(_byte) - 55; 34 | } else if (uint8(_byte) >= 97 && uint8(_byte) <= 102) { 35 | return uint8(_byte) - 87; 36 | } else { 37 | revert AddressString__InvalidByteValue(_byte); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /contracts/managers/GlacisRemoteCounterpartManager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity 0.8.18; 4 | 5 | import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol"; 6 | import {IGlacisRemoteCounterpartManager} from "../interfaces/IGlacisRemoteCounterpartManager.sol"; 7 | 8 | error GlacisRemoteCounterpartManager__RemoteCounterpartCannotHaveChainIdZero(); 9 | error GlacisRemoteCounterpartManager__CounterpartsAndChainIDsMustHaveSameLength(); 10 | 11 | /// @title Glacis Remote Counterpart Manager 12 | /// @notice An inheritable contract that allows an owner to add and remove remote counterparts 13 | /// @notice Is an ownable contract 14 | contract GlacisRemoteCounterpartManager is 15 | IGlacisRemoteCounterpartManager, 16 | Ownable2Step 17 | { 18 | mapping(uint256 => bytes32) internal remoteCounterpart; 19 | 20 | /// @notice Adds an authorized glacis counterpart component in a remote chain that interacts with this component 21 | /// @param chainIDs An array with chains of the glacis remote components 22 | /// @param counterpart An array of addresses of the glacis components on remote chains 23 | function addRemoteCounterparts( 24 | uint256[] calldata chainIDs, 25 | bytes32[] calldata counterpart 26 | ) external onlyOwner { 27 | if (chainIDs.length != counterpart.length) 28 | revert GlacisRemoteCounterpartManager__CounterpartsAndChainIDsMustHaveSameLength(); 29 | for (uint256 i; i < chainIDs.length; ++i) { 30 | if (chainIDs[i] == 0) 31 | revert GlacisRemoteCounterpartManager__RemoteCounterpartCannotHaveChainIdZero(); 32 | remoteCounterpart[chainIDs[i]] = counterpart[i]; 33 | } 34 | } 35 | 36 | /// @notice Removes an authorized glacis counterpart component on remote chain that this components interacts with 37 | /// @param chainId The chainId to remove the remote component 38 | function removeRemoteCounterpart(uint256 chainId) external onlyOwner { 39 | if (chainId == 0) 40 | revert GlacisRemoteCounterpartManager__RemoteCounterpartCannotHaveChainIdZero(); 41 | delete remoteCounterpart[chainId]; 42 | } 43 | 44 | /// @notice Gets an authorized glacis counterpart component on remote chain that this components interacts with 45 | /// @param chainId The chainId to of the remote component 46 | function getRemoteCounterpart( 47 | uint256 chainId 48 | ) public view returns (bytes32) { 49 | return remoteCounterpart[chainId]; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /contracts/mediators/SimpleTokenMediator.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity 0.8.18; 4 | 5 | import {IGlacisRouter} from "../interfaces/IGlacisRouter.sol"; 6 | import {GlacisClient} from "../client/GlacisClient.sol"; 7 | import {IXERC20} from "../interfaces/IXERC20.sol"; 8 | import {GlacisCommons} from "../commons/GlacisCommons.sol"; 9 | import {GlacisRemoteCounterpartManager} from "../managers/GlacisRemoteCounterpartManager.sol"; 10 | import {AddressBytes32} from "../libraries/AddressBytes32.sol"; 11 | 12 | error SimpleTokenMediator__DestinationChainUnavailable(); 13 | 14 | /// @title Simple Token Mediator 15 | /// @notice This contract burns and mints XERC-20 tokens without additional 16 | /// features. There is no additional Glacis XERC-20 interface, tokens cannot 17 | /// be sent with a payload, and there is no special interface for a client to 18 | /// inherit from. 19 | /// The `route` function has been replaced with a `sendCrossChain` 20 | /// function to differentiate it from the routing with payload that the 21 | /// GlacisTokenMediator has. Similarly, the retry function has been replaced 22 | /// with a `sendCrossChainRetry`. 23 | /// Developers using this must ensure that their token has the same address on 24 | /// each chain. 25 | contract SimpleTokenMediator is GlacisRemoteCounterpartManager, GlacisClient { 26 | using AddressBytes32 for address; 27 | using AddressBytes32 for bytes32; 28 | 29 | event SimpleTokenMediator__TokensMinted(address indexed, address indexed, uint256); 30 | event SimpleTokenMediator__TokensBurnt(address indexed, address indexed, uint256); 31 | 32 | constructor( 33 | address _glacisRouter, 34 | uint256 _quorum, 35 | address _owner 36 | ) GlacisClient(_glacisRouter, _quorum) { 37 | _transferOwnership(_owner); 38 | } 39 | 40 | address public xERC20Token; 41 | 42 | /// @notice Allows the owner to set the single xERC20 that this mediator sends 43 | /// @param _xERC20Token The address of the token that this mediator sends 44 | function setXERC20(address _xERC20Token) public onlyOwner { 45 | xERC20Token = _xERC20Token; 46 | } 47 | 48 | /// @notice Routes the payload to the specific address on destination chain through GlacisRouter using GMPs 49 | /// specified in gmps array 50 | /// @param chainId Destination chain (Glacis chain ID) 51 | /// @param to Destination address on remote chain 52 | /// @param adapters The GMP Adapters to use for routing 53 | /// @param fees Payment for each GMP to cover source and destination gas fees (excess will be refunded) 54 | /// @param refundAddress Address to refund excess gas payment 55 | /// @param tokenAmount Amount of token to send to remote contract 56 | function sendCrossChain( 57 | uint256 chainId, 58 | bytes32 to, 59 | address[] memory adapters, 60 | CrossChainGas[] memory fees, 61 | address refundAddress, 62 | uint256 tokenAmount 63 | ) public payable virtual returns (bytes32, uint256) { 64 | bytes32 destinationTokenMediator = remoteCounterpart[chainId]; 65 | if (destinationTokenMediator == bytes32(0)) 66 | revert SimpleTokenMediator__DestinationChainUnavailable(); 67 | 68 | IXERC20(xERC20Token).burn(msg.sender, tokenAmount); 69 | bytes memory tokenPayload = packTokenPayload(to, tokenAmount); 70 | emit SimpleTokenMediator__TokensBurnt( 71 | msg.sender, 72 | xERC20Token, 73 | tokenAmount 74 | ); 75 | return 76 | IGlacisRouter(GLACIS_ROUTER).route{value: msg.value}( 77 | chainId, 78 | destinationTokenMediator, 79 | tokenPayload, 80 | adapters, 81 | fees, 82 | refundAddress, 83 | true // Token Mediator always enables retry 84 | ); 85 | } 86 | 87 | /// @notice Retries routing the payload to the specific address on destination chain using specified GMPs 88 | /// @param chainId Destination chain (Glacis chain ID) 89 | /// @param to Destination address on remote chain 90 | /// @param adapters The GMP Adapters to use for routing 91 | /// @param fees Payment for each GMP to cover source and destination gas fees (excess will be refunded) 92 | /// @param refundAddress Address to refund excess gas payment 93 | /// @param messageId The message ID of the message to retry 94 | /// @param nonce The nonce emitted by the original message routing 95 | /// @param tokenAmount Amount of token to send to remote contract 96 | /// @return A tuple with a bytes32 messageId and a uint256 nonce 97 | function sendCrossChainRetry( 98 | uint256 chainId, 99 | bytes32 to, 100 | address[] memory adapters, 101 | CrossChainGas[] memory fees, 102 | address refundAddress, 103 | bytes32 messageId, 104 | uint256 nonce, 105 | uint256 tokenAmount 106 | ) public payable virtual returns (bytes32, uint256) { 107 | // Pack with a function 108 | bytes memory tokenPayload = packTokenPayload(to, tokenAmount); 109 | 110 | // Use helper function (otherwise stack too deep) 111 | return 112 | _routeRetry( 113 | chainId, 114 | tokenPayload, 115 | adapters, 116 | fees, 117 | refundAddress, 118 | messageId, 119 | nonce 120 | ); 121 | } 122 | 123 | /// A private function to help with stack to deep during retries. 124 | function _routeRetry( 125 | uint256 chainId, 126 | bytes memory tokenPayload, 127 | address[] memory adapters, 128 | CrossChainGas[] memory fees, 129 | address refundAddress, 130 | bytes32 messageId, 131 | uint256 nonce 132 | ) private returns (bytes32, uint256) { 133 | bytes32 destinationTokenMediator = remoteCounterpart[chainId]; 134 | if (destinationTokenMediator == bytes32(0)) 135 | revert SimpleTokenMediator__DestinationChainUnavailable(); 136 | 137 | return 138 | IGlacisRouter(GLACIS_ROUTER).routeRetry{value: msg.value}( 139 | chainId, 140 | destinationTokenMediator, 141 | tokenPayload, 142 | adapters, 143 | fees, 144 | refundAddress, 145 | messageId, 146 | nonce 147 | ); 148 | } 149 | 150 | /// @notice Receives a cross chain message from an IGlacisAdapter. 151 | /// @param payload Received payload from Glacis Router 152 | function _receiveMessage( 153 | address[] memory, // fromAdapters 154 | uint256, // fromChainId 155 | bytes32, // fromAddress 156 | bytes memory payload 157 | ) internal override { 158 | // Access control security is handled by allowed routes. No need to check for remoteCounterpart 159 | 160 | (bytes32 to, uint256 tokenAmount) = decodeTokenPayload(payload); 161 | 162 | // Mint 163 | address toAddress = to.toAddress(); 164 | IXERC20(xERC20Token).mint(toAddress, tokenAmount); 165 | emit SimpleTokenMediator__TokensMinted( 166 | toAddress, 167 | xERC20Token, 168 | tokenAmount 169 | ); 170 | } 171 | 172 | /// Packs a token payload into this contract's standard. 173 | function packTokenPayload( 174 | bytes32 to, 175 | uint256 tokenAmount 176 | ) internal pure returns (bytes memory) { 177 | return abi.encode(to, tokenAmount); 178 | } 179 | 180 | /// Decodes a token payload into this contract's standard. 181 | function decodeTokenPayload( 182 | bytes memory payload 183 | ) internal pure returns (bytes32 to, uint256 tokenAmount) { 184 | (to, tokenAmount) = abi.decode(payload, (bytes32, uint256)); 185 | } 186 | 187 | /// @notice Add an allowed route for this client 188 | /// @param allowedRoute Route to be added 189 | function addAllowedRoute( 190 | GlacisCommons.GlacisRoute memory allowedRoute 191 | ) external onlyOwner { 192 | _addAllowedRoute(allowedRoute); 193 | } 194 | 195 | /// @notice Removes an allowed route for this client 196 | /// @param route Allowed route to be removed 197 | function removeAllowedRoute( 198 | GlacisCommons.GlacisRoute calldata route 199 | ) external onlyOwner { 200 | _removeAllowedRoute(route); 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /contracts/routers/GlacisAbstractRouter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity 0.8.18; 4 | 5 | import {GlacisCommons} from "../commons/GlacisCommons.sol"; 6 | import {IGlacisRouterEvents} from "../interfaces/IGlacisRouter.sol"; 7 | import {AddressBytes32} from "../libraries/AddressBytes32.sol"; 8 | import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol"; 9 | 10 | error GlacisAbstractRouter__InvalidAdapterAddress(); //0xa46f71e2 11 | error GlacisAbstractRouter__GMPIDCannotBeZero(); //0x4332f55b 12 | error GlacisAbstractRouter__GMPIDTooHigh(); 13 | 14 | /// @title Glacis Abstract Router 15 | /// @notice A base class for the GlacisRouter 16 | abstract contract GlacisAbstractRouter is 17 | GlacisCommons, 18 | IGlacisRouterEvents, 19 | Ownable2Step 20 | { 21 | using AddressBytes32 for address; 22 | 23 | uint256 internal immutable GLACIS_CHAIN_ID; 24 | 25 | mapping(uint8 => address) public glacisGMPIdToAdapter; 26 | mapping(address => uint8) public adapterToGlacisGMPId; 27 | uint256 private nonce; 28 | 29 | /// @param chainID The chain ID that will be injected in messages 30 | constructor(uint256 chainID) { 31 | // @dev Must store chain ID due to possibility of hard fork 32 | GLACIS_CHAIN_ID = chainID; 33 | } 34 | 35 | /// @notice Registers a GMP adapter 36 | /// @param glacisGMPId The Glacis ID of the GMP 37 | /// @param glacisAdapter The address of the deployed adapter 38 | function registerAdapter( 39 | uint8 glacisGMPId, 40 | address glacisAdapter 41 | ) external virtual onlyOwner { 42 | if (glacisAdapter == address(0)) 43 | revert GlacisAbstractRouter__InvalidAdapterAddress(); 44 | if (glacisGMPId == 0) revert GlacisAbstractRouter__GMPIDCannotBeZero(); 45 | if (glacisGMPId > GlacisCommons.GLACIS_RESERVED_IDS) revert GlacisAbstractRouter__GMPIDTooHigh(); 46 | 47 | // Unregister previous adapter 48 | address previousAdapter = glacisGMPIdToAdapter[glacisGMPId]; 49 | delete adapterToGlacisGMPId[previousAdapter]; 50 | delete glacisGMPIdToAdapter[glacisGMPId]; 51 | 52 | // Adds new adapter 53 | glacisGMPIdToAdapter[glacisGMPId] = glacisAdapter; 54 | adapterToGlacisGMPId[glacisAdapter] = glacisGMPId; 55 | 56 | emit GlacisAbstractRouter__AdapterRegistered(glacisGMPId, glacisAdapter, previousAdapter); 57 | } 58 | 59 | /// @notice Unregisters a GMP adapter 60 | /// @param glacisGMPId The Glacis ID of the GMP 61 | function unRegisterAdapter( 62 | uint8 glacisGMPId 63 | ) external virtual onlyOwner { 64 | address adapter = glacisGMPIdToAdapter[glacisGMPId]; 65 | if (adapter == address(0)) 66 | revert GlacisAbstractRouter__InvalidAdapterAddress(); 67 | if (glacisGMPId == 0) revert GlacisAbstractRouter__GMPIDCannotBeZero(); 68 | 69 | delete glacisGMPIdToAdapter[glacisGMPId]; 70 | delete adapterToGlacisGMPId[adapter]; 71 | 72 | emit GlacisAbstractRouter__AdapterUnregistered(glacisGMPId, adapter); 73 | } 74 | 75 | /// @notice Creates a messageId 76 | /// @dev this Id is used to Identify identical messages on the destination chain and to verify that a retry message 77 | /// has identical data 78 | /// @param toChainId The destination chain of the message 79 | /// @param to The destination address of the message 80 | /// @param payload The payload of the message 81 | /// @return messageId , messageNonce : The message Id and the message nonce, this two parameters will be required 82 | /// if implementing message retrying 83 | function _createGlacisMessageId( 84 | uint256 toChainId, 85 | bytes32 to, 86 | bytes memory payload 87 | ) internal returns (bytes32 messageId, uint256 messageNonce) { 88 | messageNonce = nonce++; 89 | messageId = keccak256( 90 | abi.encode( 91 | toChainId, 92 | GLACIS_CHAIN_ID, 93 | to, 94 | keccak256(payload), 95 | msg.sender, 96 | messageNonce 97 | ) 98 | ); 99 | emit GlacisAbstractRouter__MessageIdCreated( 100 | messageId, 101 | msg.sender.toBytes32(), 102 | messageNonce 103 | ); 104 | } 105 | 106 | /// @notice Validates a message Id 107 | /// @param messageId The Message Id of the message 108 | /// @param toChainId The destination chain of the message 109 | /// @param fromChainId The source chain of the message 110 | /// @param to The destination address of the message 111 | /// @param messageNonce The nonce of the message 112 | /// @param payload The payload of the message 113 | /// @return true if the message Id is valid, false otherwise 114 | function validateGlacisMessageId( 115 | bytes32 messageId, 116 | uint256 toChainId, 117 | uint256 fromChainId, 118 | bytes32 to, 119 | uint256 messageNonce, 120 | bytes memory payload 121 | ) public view returns (bool) { 122 | return 123 | _validateGlacisMessageId( 124 | messageId, 125 | toChainId, 126 | fromChainId, 127 | to, 128 | msg.sender.toBytes32(), 129 | messageNonce, 130 | payload 131 | ); 132 | } 133 | 134 | /// @notice Validates a message Id 135 | /// @param messageId The Message Id of the message 136 | /// @param toChainId The destination chain of the message 137 | /// @param fromChainId The source chain of the message 138 | /// @param to The destination address of the message 139 | /// @param messageSender The sender of the message 140 | /// @param messageNonce The nonce of the message 141 | /// @param payload The payload of the message 142 | /// @return true if the message Id is valid, false otherwise 143 | function _validateGlacisMessageId( 144 | bytes32 messageId, 145 | uint256 toChainId, 146 | uint256 fromChainId, 147 | bytes32 to, 148 | bytes32 messageSender, 149 | uint256 messageNonce, 150 | bytes memory payload 151 | ) internal pure returns (bool) { 152 | bytes32 id = keccak256( 153 | abi.encode( 154 | toChainId, 155 | fromChainId, 156 | to, 157 | keccak256(payload), 158 | messageSender, 159 | messageNonce 160 | ) 161 | ); 162 | 163 | return id == messageId; 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /contracts/token/XERC20Lockbox.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.18; 3 | 4 | import {IXERC20} from "../interfaces/IXERC20.sol"; 5 | import {IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 6 | import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 7 | import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; 8 | import {IXERC20Lockbox} from "../interfaces/IXERC20Lockbox.sol"; 9 | 10 | contract XERC20Lockbox is IXERC20Lockbox { 11 | using SafeERC20 for IERC20; 12 | using SafeCast for uint256; 13 | 14 | /** 15 | * @notice The XERC20 token of this contract 16 | */ 17 | IXERC20 public immutable XERC20; 18 | 19 | /** 20 | * @notice The ERC20 token of this contract 21 | */ 22 | IERC20 public immutable ERC20; 23 | 24 | /** 25 | * @notice Whether the ERC20 token is the native gas token of this chain 26 | */ 27 | 28 | bool public immutable IS_NATIVE; 29 | 30 | /** 31 | * @notice Constructor 32 | * 33 | * @param _xerc20 The address of the XERC20 contract 34 | * @param _erc20 The address of the ERC20 contract 35 | */ 36 | 37 | constructor(address _xerc20, address _erc20, bool _isNative) { 38 | XERC20 = IXERC20(_xerc20); 39 | ERC20 = IERC20(_erc20); 40 | IS_NATIVE = _isNative; 41 | } 42 | 43 | /** 44 | * @notice Deposit native tokens into the lockbox 45 | */ 46 | 47 | function depositNative() public payable { 48 | if (!IS_NATIVE) revert IXERC20Lockbox_NotNative(); 49 | 50 | _deposit(msg.sender, msg.value); 51 | } 52 | 53 | /** 54 | * @notice Deposit ERC20 tokens into the lockbox 55 | * 56 | * @param _amount The amount of tokens to deposit 57 | */ 58 | 59 | function deposit(uint256 _amount) external { 60 | if (IS_NATIVE) revert IXERC20Lockbox_Native(); 61 | 62 | _deposit(msg.sender, _amount); 63 | } 64 | 65 | /** 66 | * @notice Deposit ERC20 tokens into the lockbox, and send the XERC20 to a user 67 | * 68 | * @param _to The user to send the XERC20 to 69 | * @param _amount The amount of tokens to deposit 70 | */ 71 | 72 | function depositTo(address _to, uint256 _amount) external { 73 | if (IS_NATIVE) revert IXERC20Lockbox_Native(); 74 | 75 | _deposit(_to, _amount); 76 | } 77 | 78 | /** 79 | * @notice Deposit the native asset into the lockbox, and send the XERC20 to a user 80 | * 81 | * @param _to The user to send the XERC20 to 82 | */ 83 | 84 | function depositNativeTo(address _to) public payable { 85 | if (!IS_NATIVE) revert IXERC20Lockbox_NotNative(); 86 | 87 | _deposit(_to, msg.value); 88 | } 89 | 90 | /** 91 | * @notice Withdraw ERC20 tokens from the lockbox 92 | * 93 | * @param _amount The amount of tokens to withdraw 94 | */ 95 | 96 | function withdraw(uint256 _amount) external { 97 | _withdraw(msg.sender, _amount); 98 | } 99 | 100 | /** 101 | * @notice Withdraw tokens from the lockbox 102 | * 103 | * @param _to The user to withdraw to 104 | * @param _amount The amount of tokens to withdraw 105 | */ 106 | 107 | function withdrawTo(address _to, uint256 _amount) external { 108 | _withdraw(_to, _amount); 109 | } 110 | 111 | /** 112 | * @notice Withdraw tokens from the lockbox 113 | * 114 | * @param _to The user to withdraw to 115 | * @param _amount The amount of tokens to withdraw 116 | */ 117 | 118 | function _withdraw(address _to, uint256 _amount) internal { 119 | emit Withdraw(_to, _amount); 120 | 121 | XERC20.burn(msg.sender, _amount); 122 | 123 | if (IS_NATIVE) { 124 | (bool _success, ) = payable(_to).call{value: _amount}(""); 125 | if (!_success) revert IXERC20Lockbox_WithdrawFailed(); 126 | } else { 127 | ERC20.safeTransfer(_to, _amount); 128 | } 129 | } 130 | 131 | /** 132 | * @notice Deposit tokens into the lockbox 133 | * 134 | * @param _to The address to send the XERC20 to 135 | * @param _amount The amount of tokens to deposit 136 | */ 137 | 138 | function _deposit(address _to, uint256 _amount) internal { 139 | if (!IS_NATIVE) { 140 | ERC20.safeTransferFrom(msg.sender, address(this), _amount); 141 | } 142 | 143 | XERC20.mint(_to, _amount); 144 | emit Deposit(_to, _amount); 145 | } 146 | 147 | receive() external payable { 148 | depositNative(); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = 'contracts' 3 | out = 'out' 4 | libs = ['lib'] 5 | test = 'test' 6 | cache_path = 'cache_forge' 7 | solc-version = "0.8.18" 8 | optimizer = true 9 | optimizer-runs = 10_000_000 -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "glacisv1", 3 | "scripts": { 4 | "test": "forge test", 5 | "coverage": "forge coverage --report lcov --report-file coverage/lcov.info; lcov --rc lcov_branch_coverage=1 --remove coverage/lcov.info --output-file coverage/filtered-lcov.info '*test*' '*node_modules*' '*GlacisLayerZeroAdapter.sol*' '*SimpleNonblockingLzApp.sol*' '*GlacisLayerZeroAdapter.sol*' '*OApp*'; genhtml --branch-coverage coverage/filtered-lcov.info -output coverage/lcov.html", 6 | "test:forge:debug": "forge test -vvv", 7 | "test:gas": "bash scripts/plotGas.sh", 8 | "hint": "solhint 'contracts/**/*.sol' 'test/**/*.sol'", 9 | "prettier": "prettier --write --plugin=prettier-plugin-solidity 'contracts/**/*.sol' 'test/**/*.sol'" 10 | }, 11 | "dependencies": { 12 | "@axelar-network/axelar-cgp-solidity": "^6.4.0", 13 | "@axelar-network/axelar-gmp-sdk-solidity": "^5.3.0", 14 | "@axelar-network/axelar-local-dev": "^2.0.0", 15 | "@chainlink/contracts-ccip": "1.2.1", 16 | "@hyperlane-xyz/core": "^3.5.1", 17 | "@layerzerolabs/oapp-evm": "https://gitpkg.vercel.app/glacislabs/lz-devtools/packages/oapp-evm?main", 18 | "@layerzerolabs/solidity-examples": "github:LayerZero-Labs/solidity-examples", 19 | "@openzeppelin/contracts": "^4.5.0", 20 | "@openzeppelin/contracts-upgradeable": "^4.5.0", 21 | "prettier": "^3.0.3", 22 | "prettier-plugin-solidity": "^1.1.3", 23 | "solhint": "^5.0.1" 24 | } 25 | } -------------------------------------------------------------------------------- /prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["prettier-plugin-solidity"], 3 | "overrides": [ 4 | { 5 | "files": "*.sol", 6 | "options": { 7 | "parser": "solidity-parse", 8 | "printWidth": 120, 9 | "tabWidth": 4, 10 | "useTabs": false, 11 | "singleQuote": false, 12 | "bracketSpacing": false 13 | } 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /remappings.txt: -------------------------------------------------------------------------------- 1 | forge-std/=lib/forge-std/src/ 2 | @arbitrum/=node_modules/@arbitrum/ 3 | @axelar-network/=node_modules/@axelar-network/ 4 | @chainlink/=node_modules/@chainlink/ 5 | @eth-optimism/=node_modules/@eth-optimism/ 6 | @hyperlane-xyz/=node_modules/@hyperlane-xyz/ 7 | @layerzerolabs/=node_modules/@layerzerolabs/ 8 | @offchainlabs/=node_modules/@offchainlabs/ 9 | @openzeppelin-3/=node_modules/@openzeppelin-3/ 10 | @openzeppelin/=node_modules/@openzeppelin/ 11 | @scroll-tech/=node_modules/@scroll-tech/ 12 | erc721a/=node_modules/erc721a/ 13 | eth-gas-reporter/=node_modules/eth-gas-reporter/ 14 | fx-portal/=node_modules/fx-portal/ 15 | hardhat-deploy/=node_modules/hardhat-deploy/ 16 | hardhat/=node_modules/hardhat/ 17 | solidity-bytes-utils/=node_modules/solidity-bytes-utils/ -------------------------------------------------------------------------------- /scripts/check-coverage.sh: -------------------------------------------------------------------------------- 1 | LINE_COVERAGE=$1 2 | FUNCTION_COVERAGE=$2 3 | BRANCH_COVERAGE=$3 4 | 5 | genhtml --branch-coverage coverage/filtered-lcov.info -output coverage/lcov.html | grep "\.:" > coverage/lcov.txt 6 | 7 | sed -i 's/\.*:\ /=/g' coverage/lcov.txt 8 | sed -i 's/\..*//g' coverage/lcov.txt 9 | 10 | . coverage/lcov.txt 11 | echo "Checking PR coverage "`date` 12 | echo 13 | echo "Requested coverage" 14 | echo "------------------" 15 | echo "Line coverage: $LINE_COVERAGE%" 16 | echo "Function coverage: $FUNCTION_COVERAGE%" 17 | echo "Branch coverage: $BRANCH_COVERAGE%" 18 | echo 19 | echo "Current coverage" 20 | echo "------------------" 21 | echo "Line coverage: $lines%" 22 | echo "Function coverage: $functions%" 23 | echo "Branch coverage: $branches%" 24 | echo 25 | echo "Coverage report" 26 | echo "------------------" 27 | 28 | if [ $lines -lt $LINE_COVERAGE ] ; then 29 | echo "Line test coverage is below $LINE_COVERAGE% - PR Coverage check failed" 30 | exit 1 31 | fi 32 | 33 | if [ $functions -lt $FUNCTION_COVERAGE ] ; then 34 | echo "Function test coverage is below $FUNCTION_COVERAGE% - PR Coverage check failed" 35 | exit 1 36 | fi 37 | 38 | if [ $branches -lt $BRANCH_COVERAGE ]; then 39 | echo "Branch test coverage is below $BRANCH_COVERAGE% - PR Coverage check failed" 40 | exit 1 41 | fi 42 | 43 | echo "PR Coverage check passed" -------------------------------------------------------------------------------- /scripts/gen-coverage.sh: -------------------------------------------------------------------------------- 1 | forge coverage --report lcov 2 | lcov --rc lcov_branch_coverage=1 --remove lcov.info 'test/*' -o forge-pruned-lcov.info 3 | genhtml ./forge-pruned-lcov.info -o report --rc lcov_branch_coverage=1 -------------------------------------------------------------------------------- /test/GlacisMockSetup.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity ^0.8.18; 3 | 4 | import {GlacisRouter} from "../contracts/routers/GlacisRouter.sol"; 5 | import {GlacisTokenMediator} from "../contracts/mediators/GlacisTokenMediator.sol"; 6 | import {GlacisCommons} from "../contracts/commons/GlacisCommons.sol"; 7 | import {AddressBytes32} from "../contracts/libraries/AddressBytes32.sol"; 8 | import {AxelarGatewayMock} from "./contracts/mocks/axelar/AxelarGatewayMock.sol"; 9 | import {AxelarGasServiceMock} from "./contracts/mocks/axelar/AxelarGasServiceMock.sol"; 10 | import {LayerZeroV2Mock} from "./contracts/mocks/lz/LayerZeroV2Mock.sol"; 11 | import {WormholeRelayerMock} from "./contracts/mocks/wormhole/WormholeRelayerMock.sol"; 12 | import {CCIPRouterMock} from "./contracts/mocks/ccip/CCIPRouterMock.sol"; 13 | import {HyperlaneMailboxMock} from "./contracts/mocks/hyperlane/HyperlaneMailboxMock.sol"; 14 | import {GlacisAxelarAdapter} from "../contracts/adapters/GlacisAxelarAdapter.sol"; 15 | import {GlacisLayerZeroV2Adapter} from "../contracts/adapters/LayerZero/GlacisLayerZeroV2Adapter.sol"; 16 | import {GlacisWormholeAdapter} from "../contracts/adapters/Wormhole/GlacisWormholeAdapter.sol"; 17 | import {GlacisHyperlaneAdapter} from "../contracts/adapters/GlacisHyperlaneAdapter.sol"; 18 | import {GlacisCCIPAdapter} from "../contracts/adapters/GlacisCCIPAdapter.sol"; 19 | 20 | // TODO: give all mocks a failure feature that allows to check for retries 21 | 22 | contract GlacisMockSetup is GlacisCommons { 23 | using AddressBytes32 for address; 24 | using AddressBytes32 for bytes32; 25 | 26 | address public constant AXELAR_GMP_ID = address(1); 27 | address public constant LAYERZERO_GMP_ID = address(2); 28 | address public constant WORMHOLE_GMP_ID = address(3); 29 | address public constant CCIP_GMP_ID = address(4); 30 | address public constant HYPERLANE_GMP_ID = address(5); 31 | 32 | // glacisRouter 33 | GlacisRouter public glacisRouter; 34 | 35 | // Mediators 36 | // GlacisTokenMediator public glacisTokenMediator; 37 | 38 | // Mocks 39 | AxelarGatewayMock public axelarGatewayMock; 40 | AxelarGasServiceMock public axelarGasServiceMock; 41 | LayerZeroV2Mock public layerZeroMock; 42 | WormholeRelayerMock public wormholeRelayerMock; 43 | CCIPRouterMock public ccipMock; 44 | HyperlaneMailboxMock public hyperlaneMock; 45 | 46 | // Adapters 47 | GlacisAxelarAdapter public glacisAxelarAdapter; 48 | GlacisLayerZeroV2Adapter public glacisLayerZeroV2Adapter; 49 | GlacisWormholeAdapter public glacisWormholeAdapter; 50 | GlacisCCIPAdapter public glacisCCIPAdapter; 51 | GlacisHyperlaneAdapter public glacisHyperlaneAdapter; 52 | 53 | constructor() { 54 | glacisRouter = new GlacisRouter(address(this)); 55 | // glacisTokenMediator = new GlacisTokenMediator(); 56 | } 57 | 58 | /// Deploys and sets up a new adapter for Axelar 59 | function setupAxelar() external returns (GlacisAxelarAdapter adapter) { 60 | axelarGatewayMock = new AxelarGatewayMock(); 61 | axelarGasServiceMock = new AxelarGasServiceMock(); 62 | 63 | // Deploy adapter 64 | adapter = new GlacisAxelarAdapter( 65 | address(glacisRouter), 66 | address(axelarGatewayMock), 67 | address(axelarGasServiceMock), 68 | address(this) 69 | ); 70 | glacisAxelarAdapter = adapter; 71 | 72 | // Add adapter to the glacisRouter 73 | glacisRouter.registerAdapter(uint8(uint160(AXELAR_GMP_ID)), address(adapter)); 74 | 75 | // Adds a glacisId => axelar chain string configuration to the adapter 76 | uint256[] memory glacisIDs = new uint256[](1); 77 | glacisIDs[0] = block.chainid; 78 | string[] memory axelarLabels = new string[](1); 79 | axelarLabels[0] = "Anvil"; 80 | 81 | adapter.setGlacisChainIds(glacisIDs, axelarLabels); 82 | bytes32[] memory adapterCounterparts = new bytes32[](1); 83 | adapterCounterparts[0] = address(adapter).toBytes32(); 84 | adapter.addRemoteCounterparts(glacisIDs, adapterCounterparts); 85 | } 86 | 87 | /// Deploys and sets up a new adapter for LayerZero 88 | function setupLayerZero() external returns (GlacisLayerZeroV2Adapter adapter) { 89 | layerZeroMock = new LayerZeroV2Mock(); 90 | 91 | adapter = new GlacisLayerZeroV2Adapter( 92 | address(glacisRouter), 93 | address(layerZeroMock), 94 | address(this) 95 | ); 96 | glacisLayerZeroV2Adapter = adapter; 97 | 98 | // Register lzID <-> glacisID 99 | uint32[] memory lzIDs = new uint32[](1); 100 | lzIDs[0] = layerZeroMock.getChainId(); 101 | uint256[] memory glacisIDs = new uint256[](1); 102 | glacisIDs[0] = uint32(block.chainid); 103 | adapter.setGlacisChainIds(glacisIDs, lzIDs); 104 | 105 | // Add self as a remote counterpart 106 | bytes32[] memory adapterCounterparts = new bytes32[](1); 107 | adapterCounterparts[0] = address(adapter).toBytes32(); 108 | adapter.addRemoteCounterparts(glacisIDs, adapterCounterparts); 109 | 110 | // Register adapter in GlacisRouter 111 | glacisRouter.registerAdapter(uint8(uint160(LAYERZERO_GMP_ID)), address(adapter)); 112 | } 113 | 114 | /// Deploys and sets up adapters for Wormhole 115 | function setupWormhole() external returns (GlacisWormholeAdapter adapter) { 116 | wormholeRelayerMock = new WormholeRelayerMock(); 117 | 118 | adapter = new GlacisWormholeAdapter( 119 | glacisRouter, 120 | address(wormholeRelayerMock), 121 | uint16(block.chainid), 122 | address(this) 123 | ); 124 | glacisWormholeAdapter = adapter; 125 | 126 | uint256[] memory glacisIDs = new uint256[](1); 127 | glacisIDs[0] = block.chainid; 128 | uint16[] memory wormholeIDs = new uint16[](1); 129 | wormholeIDs[0] = 1; 130 | 131 | adapter.setGlacisChainIds(glacisIDs, wormholeIDs); 132 | 133 | glacisRouter.registerAdapter(uint8(uint160(WORMHOLE_GMP_ID)), address(adapter)); 134 | bytes32[] memory adapterCounterparts = new bytes32[](1); 135 | adapterCounterparts[0] = address(adapter).toBytes32(); 136 | adapter.addRemoteCounterparts(glacisIDs, adapterCounterparts); 137 | 138 | return adapter; 139 | } 140 | 141 | /// Deploys and sets up adapters for Hyperlane 142 | function setupCCIP() external returns (GlacisCCIPAdapter adapter) { 143 | ccipMock = new CCIPRouterMock(); 144 | 145 | adapter = new GlacisCCIPAdapter( 146 | address(glacisRouter), 147 | address(ccipMock), 148 | address(this) 149 | ); 150 | glacisCCIPAdapter = adapter; 151 | 152 | uint256[] memory glacisIDs = new uint256[](1); 153 | glacisIDs[0] = block.chainid; 154 | uint64[] memory chainSelectors = new uint64[](1); 155 | chainSelectors[0] = uint64(block.chainid); 156 | 157 | adapter.setGlacisChainIds(glacisIDs, chainSelectors); 158 | glacisRouter.registerAdapter(uint8(uint160(CCIP_GMP_ID)), address(adapter)); 159 | bytes32[] memory adapterCounterparts = new bytes32[](1); 160 | adapterCounterparts[0] = address(adapter).toBytes32(); 161 | adapter.addRemoteCounterparts(glacisIDs, adapterCounterparts); 162 | 163 | return adapter; 164 | } 165 | 166 | /// Deploys and sets up adapters for Hyperlane 167 | function setupHyperlane() external returns (GlacisHyperlaneAdapter adapter) { 168 | hyperlaneMock = new HyperlaneMailboxMock(); 169 | 170 | adapter = new GlacisHyperlaneAdapter( 171 | address(glacisRouter), 172 | address(hyperlaneMock), 173 | address(this) 174 | ); 175 | glacisHyperlaneAdapter = adapter; 176 | 177 | uint256[] memory glacisIDs = new uint256[](1); 178 | glacisIDs[0] = block.chainid; 179 | uint32[] memory hyperlaneDomains = new uint32[](1); 180 | hyperlaneDomains[0] = uint32(block.chainid); 181 | 182 | adapter.setGlacisChainIds(glacisIDs, hyperlaneDomains); 183 | 184 | glacisRouter.registerAdapter(uint8(uint160(HYPERLANE_GMP_ID)), address(adapter)); 185 | bytes32[] memory adapterCounterparts = new bytes32[](1); 186 | adapterCounterparts[0] = address(adapter).toBytes32(); 187 | adapter.addRemoteCounterparts(glacisIDs, adapterCounterparts); 188 | 189 | return adapter; 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /test/client/accessControl.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity 0.8.18; 4 | import {LocalTestSetup, GlacisAxelarAdapter, GlacisRouter, AxelarGatewayMock, AxelarGasServiceMock, LayerZeroV2Mock, GlacisLayerZeroV2Adapter, WormholeRelayerMock, GlacisWormholeAdapter} from "../LocalTestSetup.sol"; 5 | import {GlacisClientSample} from "../contracts/samples/GlacisClientSample.sol"; 6 | import {AddressBytes32} from "../../contracts/libraries/AddressBytes32.sol"; 7 | import "forge-std/console.sol"; 8 | 9 | /* solhint-disable contract-name-camelcase */ 10 | contract AccessControlTests is LocalTestSetup { 11 | using AddressBytes32 for address; 12 | 13 | GlacisRouter internal glacisRouter; 14 | GlacisClientSample internal clientSample; 15 | 16 | function setUp() public { 17 | glacisRouter = deployGlacisRouter(); 18 | (clientSample, ) = deployGlacisClientSample(glacisRouter); 19 | } 20 | 21 | function test__AccessControl_AddAllowedRoute(GlacisRoute calldata route_) external { 22 | vm.assume(route_.fromAdapter != address(0)); 23 | vm.assume(route_.fromAddress != bytes32(0)); 24 | vm.assume(route_.fromChainId != 0); 25 | clientSample.addAllowedRoute(route_); 26 | assertTrue(clientSample.isAllowedRoute(route_, "")); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /test/client/adapter/adapter.axelar.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity 0.8.18; 4 | import {LocalTestSetup, GlacisAxelarAdapter, GlacisRouter, AxelarGatewayMock, AxelarGasServiceMock, GlacisCommons} from "../../LocalTestSetup.sol"; 5 | import {GlacisAbstractAdapter__OnlyAdapterAllowed, GlacisAbstractAdapter__IDArraysMustBeSameLength, GlacisAbstractAdapter__DestinationChainIdNotValid, GlacisAbstractAdapter__NoRemoteAdapterForChainId, GlacisAbstractAdapter__OnlyGlacisRouterAllowed, GlacisAbstractAdapter__SourceChainNotRegistered, GlacisAbstractAdapter__ChainIsNotAvailable} from "../../../contracts/adapters/GlacisAbstractAdapter.sol"; 6 | import {GlacisClientSample} from "../../contracts/samples/GlacisClientSample.sol"; 7 | import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; 8 | import {AddressBytes32} from "../../../contracts/libraries/AddressBytes32.sol"; 9 | 10 | /* solhint-disable contract-name-camelcase */ 11 | contract AdapterTests__Axelar is LocalTestSetup { 12 | AxelarGatewayMock internal axelarGatewayMock; 13 | AxelarGasServiceMock internal axelarGasServiceMock; 14 | GlacisAxelarAdapter internal axelarAdapter; 15 | GlacisRouter internal glacisRouter; 16 | GlacisClientSample internal clientSample; 17 | 18 | using Strings for address; 19 | using AddressBytes32 for address; 20 | 21 | function setUp() public { 22 | glacisRouter = deployGlacisRouter(); 23 | (axelarGatewayMock, axelarGasServiceMock) = deployAxelarFixture(); 24 | axelarAdapter = deployAxelarAdapters( 25 | glacisRouter, 26 | axelarGatewayMock, 27 | axelarGasServiceMock 28 | ); 29 | (clientSample, ) = deployGlacisClientSample(glacisRouter); 30 | } 31 | 32 | function test__onlyAdapterAllowedFailure_Axelar() external { 33 | // 1. Expect error 34 | vm.expectRevert(GlacisAbstractAdapter__OnlyAdapterAllowed.selector); 35 | 36 | // 2. Send fake message to GlacisAxelarAdapter through AxelarGatewayMock 37 | axelarGatewayMock.callContract( 38 | "Anvil", 39 | address(axelarAdapter).toHexString(), 40 | abi.encode( 41 | keccak256("random message ID"), 42 | // This address injection is the attack that we are trying to avoid 43 | address(axelarAdapter), 44 | address(axelarAdapter), 45 | 1, 46 | false, 47 | "my text" 48 | ) 49 | ); 50 | } 51 | 52 | function test__setGlacisChainIds_Axelar( 53 | uint256 chain, 54 | string memory name 55 | ) external { 56 | vm.assume(chain != 0); 57 | vm.assume(bytes(name).length > 0); 58 | 59 | uint256[] memory chains = new uint256[](1); 60 | chains[0] = chain; 61 | string[] memory axelarNames = new string[](1); 62 | axelarNames[0] = name; 63 | 64 | axelarAdapter.setGlacisChainIds(chains, axelarNames); 65 | 66 | assertEq(axelarAdapter.adapterChainID(chain), name); 67 | assertEq(axelarAdapter.adapterChainIdToGlacisChainId(name), chain); 68 | assertTrue(axelarAdapter.chainIsAvailable(chain)); 69 | } 70 | 71 | function test__setGlacisChainIdsErrors_Axelar( 72 | uint256 chain, 73 | string memory name 74 | ) external { 75 | vm.assume(chain != 0); 76 | vm.assume(bytes(name).length > 0); 77 | 78 | uint256[] memory chains = new uint256[](1); 79 | chains[0] = chain; 80 | string[] memory axelarNames = new string[](2); 81 | axelarNames[0] = name; 82 | axelarNames[0] = name; 83 | 84 | vm.expectRevert(GlacisAbstractAdapter__IDArraysMustBeSameLength.selector); 85 | axelarAdapter.setGlacisChainIds(chains, axelarNames); 86 | 87 | axelarNames = new string[](1); 88 | axelarNames[0] = name; 89 | chains[0] = 0; 90 | 91 | vm.expectRevert(GlacisAbstractAdapter__DestinationChainIdNotValid.selector); 92 | axelarAdapter.setGlacisChainIds(chains, axelarNames); 93 | } 94 | 95 | function test__chainIsNotAvailable_Axelar(uint256 chainId) external { 96 | vm.assume(chainId != block.chainid); 97 | assertFalse(axelarAdapter.chainIsAvailable(chainId)); 98 | } 99 | 100 | function test__toLowerCase_Axelar() external { 101 | string 102 | memory str1 = "!!!aaAabBbbcCcdDDeFGhIiJjjKkl%%%1234LmmmMnOoPpQRStTUvwwWxYyyyzZZzzz..."; 103 | string 104 | memory convertStr1 = "!!!aaaabbbbcccdddefghiijjjkkl%%%1234lmmmmnooppqrsttuvwwwxyyyyzzzzzz..."; 105 | GlacisAxelarAdapterHarness harness = deployHarness(); 106 | 107 | assertEq(harness.toLowerCase(str1), convertStr1); 108 | } 109 | 110 | function test__sendMessageChecksAvailability_Axelar(uint256 chainId) external { 111 | vm.assume(chainId != 0); 112 | GlacisAxelarAdapterHarness harness = deployHarness(); 113 | 114 | // Test no remote adapter 115 | vm.startPrank(address(glacisRouter)); 116 | vm.expectRevert(abi.encodeWithSelector(GlacisAbstractAdapter__NoRemoteAdapterForChainId.selector, chainId)); 117 | harness.sendMessagePublic( 118 | chainId, 119 | address(this), 120 | CrossChainGas(100, 100), 121 | abi.encode(0) 122 | ); 123 | 124 | // Add remote adapter to get past 125 | uint256[] memory glacisIDs = new uint256[](1); 126 | glacisIDs[0] = chainId; 127 | bytes32[] memory adapterCounterparts = new bytes32[](1); 128 | adapterCounterparts[0] = address(0x123).toBytes32(); 129 | vm.stopPrank(); 130 | harness.addRemoteCounterparts(glacisIDs, adapterCounterparts); 131 | 132 | // Test no Axelar chain label 133 | vm.startPrank(address(glacisRouter)); 134 | vm.expectRevert(abi.encodeWithSelector(GlacisAbstractAdapter__ChainIsNotAvailable.selector, chainId)); 135 | harness.sendMessagePublic( 136 | chainId, 137 | address(this), 138 | CrossChainGas(100, 100), 139 | abi.encode(0) 140 | ); 141 | } 142 | 143 | function deployHarness() private returns (GlacisAxelarAdapterHarness) { 144 | return new GlacisAxelarAdapterHarness( 145 | address(glacisRouter), 146 | address(axelarGatewayMock), 147 | address(axelarGasServiceMock), 148 | address(this) 149 | ); 150 | } 151 | } 152 | 153 | contract GlacisAxelarAdapterHarness is GlacisAxelarAdapter, GlacisCommons { 154 | constructor( 155 | address _glacisRouter, 156 | address _axelarGateway, 157 | address _axelarGasReceiver, 158 | address _owner 159 | ) 160 | GlacisAxelarAdapter( 161 | _glacisRouter, 162 | _axelarGateway, 163 | _axelarGasReceiver, 164 | _owner 165 | ) 166 | {} 167 | 168 | function toLowerCase( 169 | string memory str 170 | ) external pure returns (string memory) { 171 | return _toLowerCase(str); 172 | } 173 | 174 | function sendMessagePublic( 175 | uint256 toChainId, 176 | address refundAddress, 177 | CrossChainGas calldata gas, 178 | bytes memory payload 179 | ) external { 180 | return _sendMessage(toChainId, refundAddress, gas, payload); 181 | } 182 | 183 | function executePublic( 184 | string calldata sourceChain, 185 | string calldata sourceAddress, 186 | bytes calldata payload 187 | ) external { 188 | return _execute(sourceChain, sourceAddress, payload); 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /test/client/adapter/adapter.hyperlane.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity 0.8.18; 4 | import {LocalTestSetup, GlacisAxelarAdapter, GlacisRouter, GlacisCommons, HyperlaneMailboxMock, GlacisHyperlaneAdapter} from "../../LocalTestSetup.sol"; 5 | import {GlacisClientSample} from "../../contracts/samples/GlacisClientSample.sol"; 6 | import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; 7 | import {GlacisAbstractAdapter__OnlyAdapterAllowed, GlacisAbstractAdapter__IDArraysMustBeSameLength, GlacisAbstractAdapter__DestinationChainIdNotValid, GlacisAbstractAdapter__NoRemoteAdapterForChainId, GlacisAbstractAdapter__OnlyGlacisRouterAllowed, GlacisAbstractAdapter__SourceChainNotRegistered, GlacisAbstractAdapter__ChainIsNotAvailable} from "../../../contracts/adapters/GlacisAbstractAdapter.sol"; 8 | import {GlacisHyperlaneAdapter__OnlyMailboxAllowed, GlacisHyperlaneAdapter__FeeNotEnough} from "../../../contracts/adapters/GlacisHyperlaneAdapter.sol"; 9 | import {IGlacisAdapter} from "../../../contracts/interfaces/IGlacisAdapter.sol"; 10 | import {SimpleNonblockingLzAppEvents} from "../../../contracts/adapters/LayerZero/SimpleNonblockingLzApp.sol"; 11 | import {AddressBytes32} from "../../../contracts/libraries/AddressBytes32.sol"; 12 | 13 | // solhint-disable-next-line 14 | contract AdapterTests__Hyperlane is LocalTestSetup { 15 | HyperlaneMailboxMock internal mailboxMock; 16 | GlacisHyperlaneAdapter internal hyperlaneAdapter; 17 | GlacisRouter internal glacisRouter; 18 | GlacisClientSample internal clientSample; 19 | 20 | using Strings for address; 21 | using AddressBytes32 for address; 22 | 23 | function setUp() public { 24 | glacisRouter = deployGlacisRouter(); 25 | (mailboxMock) = deployHyperlaneFixture(); 26 | hyperlaneAdapter = deployHyperlaneAdapter( 27 | glacisRouter, 28 | mailboxMock 29 | ); 30 | (clientSample, ) = deployGlacisClientSample(glacisRouter); 31 | } 32 | 33 | function test__setGlacisChainIds_Hyperlane(uint32 chain) external { 34 | vm.assume(chain != 0); 35 | 36 | uint256[] memory chains = new uint256[](1); 37 | chains[0] = chain; 38 | uint32[] memory hyperlaneDomains = new uint32[](1); 39 | hyperlaneDomains[0] = chain; 40 | 41 | hyperlaneAdapter.setGlacisChainIds(chains, hyperlaneDomains); 42 | 43 | assertEq(hyperlaneAdapter.adapterChainID(chain), chain); 44 | assertEq(hyperlaneAdapter.adapterChainIdToGlacisChainId(chain), chain); 45 | assertTrue(hyperlaneAdapter.chainIsAvailable(chain)); 46 | } 47 | 48 | function test__setGlacisChainIdsErrors_Hyperlane(uint32 chain) external { 49 | vm.assume(chain != 0); 50 | 51 | uint256[] memory chains = new uint256[](1); 52 | chains[0] = chain; 53 | uint32[] memory hyperlaneDomains = new uint32[](2); 54 | hyperlaneDomains[0] = chain; 55 | hyperlaneDomains[0] = chain; 56 | 57 | vm.expectRevert(GlacisAbstractAdapter__IDArraysMustBeSameLength.selector); 58 | hyperlaneAdapter.setGlacisChainIds(chains, hyperlaneDomains); 59 | 60 | hyperlaneDomains = new uint32[](1); 61 | hyperlaneDomains[0] = chain; 62 | chains[0] = 0; 63 | 64 | vm.expectRevert(GlacisAbstractAdapter__DestinationChainIdNotValid.selector); 65 | hyperlaneAdapter.setGlacisChainIds(chains, hyperlaneDomains); 66 | } 67 | 68 | function test__chainIsNotAvailable_Hyperlane(uint256 chainId) external { 69 | vm.assume(chainId != block.chainid); 70 | assertFalse(hyperlaneAdapter.chainIsAvailable(chainId)); 71 | } 72 | 73 | function test__sendMessageChecksAvailability_Hyperlane(uint256 chainId) external { 74 | vm.assume(chainId != 0); 75 | GlacisHyperlaneAdapterHarness harness = deployHarness(); 76 | 77 | // Test no remote adapter 78 | vm.startPrank(address(glacisRouter)); 79 | vm.expectRevert(abi.encodeWithSelector(GlacisAbstractAdapter__NoRemoteAdapterForChainId.selector, chainId)); 80 | harness.sendMessagePublic( 81 | chainId, 82 | address(this), 83 | CrossChainGas(100, 100), 84 | abi.encode(0) 85 | ); 86 | 87 | // Add remote adapter to get past 88 | uint256[] memory glacisIDs = new uint256[](1); 89 | glacisIDs[0] = chainId; 90 | bytes32[] memory adapterCounterparts = new bytes32[](1); 91 | adapterCounterparts[0] = address(0x123).toBytes32(); 92 | vm.stopPrank(); 93 | harness.addRemoteCounterparts(glacisIDs, adapterCounterparts); 94 | 95 | // Test no domain label 96 | vm.startPrank(address(glacisRouter)); 97 | vm.expectRevert(abi.encodeWithSelector(GlacisAbstractAdapter__ChainIsNotAvailable.selector, chainId)); 98 | harness.sendMessagePublic( 99 | chainId, 100 | address(this), 101 | CrossChainGas(100, 100), 102 | abi.encode(0) 103 | ); 104 | } 105 | 106 | function test__handleOnlyAllowsMailbox_Hyperlane() external { 107 | // Add remote adapter to get past 108 | uint256[] memory glacisIDs = new uint256[](1); 109 | glacisIDs[0] = 100; 110 | uint32[] memory hyperlaneDomains = new uint32[](1); 111 | hyperlaneDomains[0] = 100; 112 | bytes32[] memory adapterCounterparts = new bytes32[](1); 113 | adapterCounterparts[0] = address(0x123).toBytes32(); 114 | hyperlaneAdapter.setGlacisChainIds(glacisIDs, hyperlaneDomains); 115 | hyperlaneAdapter.addRemoteCounterparts(glacisIDs, adapterCounterparts); 116 | 117 | // Test for bad mailbox 118 | vm.expectRevert(GlacisHyperlaneAdapter__OnlyMailboxAllowed.selector); 119 | hyperlaneAdapter.handle(100, adapterCounterparts[0], abi.encode(0)); 120 | } 121 | 122 | function test__insufficientFee_Hyperlane() external { 123 | mailboxMock.setHookFee(1 ether); 124 | 125 | // Add remote adapter to get past 126 | uint256[] memory glacisIDs = new uint256[](1); 127 | glacisIDs[0] = 100; 128 | uint32[] memory hyperlaneDomains = new uint32[](1); 129 | hyperlaneDomains[0] = 100; 130 | bytes32[] memory adapterCounterparts = new bytes32[](1); 131 | adapterCounterparts[0] = address(0x123).toBytes32(); 132 | hyperlaneAdapter.setGlacisChainIds(glacisIDs, hyperlaneDomains); 133 | hyperlaneAdapter.addRemoteCounterparts(glacisIDs, adapterCounterparts); 134 | 135 | vm.expectRevert(GlacisHyperlaneAdapter__FeeNotEnough.selector); 136 | address[] memory adapters = new address[](1); 137 | adapters[0] = HYPERLANE_GMP_ID; 138 | clientSample.setRemoteValue{ value: 1 ether - 1}( 139 | block.chainid, 140 | address(clientSample).toBytes32(), 141 | abi.encode(0), 142 | adapters, 143 | createFees(1 ether - 1, 1), 144 | address(this), 145 | false, 146 | 1 ether - 1 147 | ); 148 | } 149 | 150 | function deployHarness() private returns (GlacisHyperlaneAdapterHarness) { 151 | return new GlacisHyperlaneAdapterHarness( 152 | address(glacisRouter), 153 | address(mailboxMock), 154 | address(this) 155 | ); 156 | } 157 | } 158 | 159 | contract GlacisHyperlaneAdapterHarness is GlacisHyperlaneAdapter, GlacisCommons { 160 | constructor( 161 | address _glacisRouter, 162 | address _hyperlaneMailbox, 163 | address _owner 164 | ) GlacisHyperlaneAdapter(_glacisRouter, _hyperlaneMailbox, _owner) { } 165 | 166 | function sendMessagePublic( 167 | uint256 toChainId, 168 | address refundAddress, 169 | CrossChainGas calldata gas, 170 | bytes memory payload 171 | ) external payable { 172 | _sendMessage(toChainId, refundAddress, gas, payload); 173 | } 174 | } -------------------------------------------------------------------------------- /test/client/counterparts.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity 0.8.18; 4 | import {LocalTestSetup, GlacisAxelarAdapter, GlacisRouter, AxelarGatewayMock, AxelarGasServiceMock, LayerZeroV2Mock, GlacisLayerZeroV2Adapter, WormholeRelayerMock, GlacisWormholeAdapter, CCIPRouterMock, GlacisCCIPAdapter, HyperlaneMailboxMock, GlacisHyperlaneAdapter} from "../LocalTestSetup.sol"; 5 | import {GlacisClientSample} from "../contracts/samples/GlacisClientSample.sol"; 6 | import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; 7 | import {GlacisRemoteCounterpartManager__CounterpartsAndChainIDsMustHaveSameLength, GlacisRemoteCounterpartManager__RemoteCounterpartCannotHaveChainIdZero} from "../../contracts/managers/GlacisRemoteCounterpartManager.sol"; 8 | import {AddressBytes32} from "../../contracts/libraries/AddressBytes32.sol"; 9 | 10 | /* solhint-disable contract-name-camelcase */ 11 | contract CounterpartManagerTests is LocalTestSetup { 12 | using AddressBytes32 for address; 13 | using AddressBytes32 for bytes32; 14 | LayerZeroV2Mock internal lzGatewayMock; 15 | GlacisLayerZeroV2Adapter internal adapter; 16 | GlacisRouter internal glacisRouter; 17 | 18 | function setUp() public { 19 | glacisRouter = deployGlacisRouter(); 20 | (lzGatewayMock) = deployLayerZeroFixture(); 21 | adapter = deployLayerZeroAdapters(glacisRouter, lzGatewayMock); 22 | } 23 | 24 | function test__RemoteCounterparts_ArraysMustBeSameLength() external { 25 | uint256[] memory glacisChainIds = new uint256[](1); 26 | glacisChainIds[0] = block.chainid; 27 | bytes32[] memory adapterCounterparts = new bytes32[](2); 28 | adapterCounterparts[0] = address(adapter).toBytes32(); 29 | vm.expectRevert( 30 | GlacisRemoteCounterpartManager__CounterpartsAndChainIDsMustHaveSameLength 31 | .selector 32 | ); 33 | adapter.addRemoteCounterparts(glacisChainIds, adapterCounterparts); 34 | } 35 | 36 | function test__RemoteCounterparts_RemoteCounterpartCannotHaveChainIdZero_Add() 37 | external 38 | { 39 | uint256[] memory glacisChainIds = new uint256[](1); 40 | glacisChainIds[0] = 0; 41 | bytes32[] memory adapterCounterparts = new bytes32[](1); 42 | adapterCounterparts[0] = address(adapter).toBytes32(); 43 | vm.expectRevert( 44 | GlacisRemoteCounterpartManager__RemoteCounterpartCannotHaveChainIdZero 45 | .selector 46 | ); 47 | adapter.addRemoteCounterparts(glacisChainIds, adapterCounterparts); 48 | } 49 | 50 | function test__RemoteCounterparts_RemoteCounterpartCannotHaveChainIdZero_Remove() 51 | external 52 | { 53 | vm.expectRevert( 54 | GlacisRemoteCounterpartManager__RemoteCounterpartCannotHaveChainIdZero 55 | .selector 56 | ); 57 | adapter.removeRemoteCounterpart(0); 58 | } 59 | 60 | function test__RemoteCounterparts_Get() external { 61 | uint256[] memory glacisChainIds = new uint256[](1); 62 | glacisChainIds[0] = 1; 63 | bytes32[] memory adapterCounterparts = new bytes32[](1); 64 | adapterCounterparts[0] = address(adapter).toBytes32(); 65 | adapter.addRemoteCounterparts(glacisChainIds, adapterCounterparts); 66 | adapter.getRemoteCounterpart(glacisChainIds[0]); 67 | assertEq( 68 | adapter.getRemoteCounterpart(glacisChainIds[0]), 69 | address(adapter).toBytes32() 70 | ); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /test/client/ownable.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity 0.8.18; 4 | import {LocalTestSetup, GlacisAxelarAdapter, GlacisRouter, AxelarGatewayMock, AxelarGasServiceMock, LayerZeroV2Mock, GlacisLayerZeroV2Adapter, WormholeRelayerMock, GlacisWormholeAdapter} from "../LocalTestSetup.sol"; 5 | import {GlacisClientSample} from "../contracts/samples/GlacisClientSample.sol"; 6 | import {GlacisClientTextSample} from "../contracts/samples/GlacisClientTextSample.sol"; 7 | import {AddressBytes32} from "../../contracts/libraries/AddressBytes32.sol"; 8 | import {CustomAdapterSample} from "../contracts/samples/CustomAdapterSample.sol"; 9 | import "forge-std/console.sol"; 10 | 11 | /* solhint-disable contract-name-camelcase */ 12 | contract OwnableTests is LocalTestSetup { 13 | using AddressBytes32 for address; 14 | 15 | GlacisRouter internal glacisRouter; 16 | GlacisClientSample internal clientSample; 17 | 18 | function setUp() public { 19 | glacisRouter = deployGlacisRouter(); 20 | (clientSample, ) = deployGlacisClientSample(glacisRouter); 21 | } 22 | 23 | function test__Ownable(address newOwner) external { 24 | vm.assume(newOwner != address(0)); 25 | clientSample.transferOwnership(newOwner); 26 | assertEq(clientSample.owner(), newOwner); 27 | } 28 | 29 | function test__Ownable_ZeroAddress() external { 30 | vm.expectRevert("Ownable: new owner is the zero address"); 31 | clientSample.transferOwnership(address(0)); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /test/contracts/mocks/axelar/AxelarGasServiceMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | contract AxelarGasServiceMock { 5 | error AxelarGasServiceMock__RefundAddressDoesNotReceiveRefund(); 6 | 7 | // This is called on the source chain before calling the gateway to execute a remote contract. 8 | function payNativeGasForContractCall( 9 | address, // sender, 10 | string calldata, // destinationChain, 11 | string calldata, // destinationAddress, 12 | bytes calldata, // payload, 13 | address refundAddress 14 | ) external payable { 15 | // Refund doesn't actually send in the same transaction, so it won't actually revert. 16 | // But it's good to test it out, since the gas service just banks the cash otherwise. 17 | // https://github.com/axelarnetwork/axelar-cgp-solidity/blob/0fb933430103c863e9804b790de5caa917f61fb1/contracts/gas-service/AxelarGasService.sol#L122C1-L130C6 18 | (bool success, ) = payable(refundAddress).call{value: msg.value}(""); 19 | if (!success) { 20 | revert AxelarGasServiceMock__RefundAddressDoesNotReceiveRefund(); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test/contracts/mocks/axelar/AxelarGatewayMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | import {GlacisAxelarAdapter} from "../../../../contracts/adapters/GlacisAxelarAdapter.sol"; 4 | import {AddressString} from "../../../../contracts/libraries/AddressString.sol"; 5 | import {CheckSum} from "../../libraries/CheckSum.sol"; 6 | 7 | contract AxelarGatewayMock { 8 | using AddressString for string; 9 | using CheckSum for address; 10 | 11 | /// A function that mocks IAxelarGateway, calling the msg.sender on the same chain 12 | /// much gas contract interactions take on the destination chain. 13 | function callContract( 14 | string calldata destinationChain, 15 | string calldata destinationAddress, 16 | bytes calldata payload 17 | ) external { 18 | // Get the contract address 19 | string memory destAddrStr = destinationAddress[2:42]; // remove 0x 20 | address contractAddr = destAddrStr.toAddress(); 21 | 22 | // For better GMP simulation, we use the checksummed address just like Axelar does. 23 | string memory fromAddr = msg.sender.toChecksumString(); 24 | 25 | execute(contractAddr, destinationChain, fromAddr, payload); 26 | } 27 | 28 | function execute( 29 | address contractAddr, 30 | string calldata sourceChain, 31 | string memory sourceAddress, 32 | bytes calldata payload 33 | ) public { 34 | bytes32 commandId = 0; 35 | GlacisAxelarAdapter(contractAddr).execute( 36 | commandId, 37 | sourceChain, 38 | sourceAddress, 39 | payload 40 | ); 41 | } 42 | 43 | /// Returns true to always validate the contract call (nearly 0 gas). 44 | function validateContractCall( 45 | bytes32, 46 | string memory, 47 | string memory, 48 | bytes32 49 | ) external pure returns (bool) { 50 | return true; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /test/contracts/mocks/axelar/AxelarOneWayGatewayMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | import {GlacisAxelarAdapter} from "../../../../contracts/adapters/GlacisAxelarAdapter.sol"; 4 | import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; 5 | 6 | contract AxelarOneWayGatewayMock { 7 | using Strings for address; 8 | 9 | /// A function that is meant to do nothing, helping tests figure out how 10 | /// much gas contract interactions take on the destination chain. 11 | function callContract( 12 | string calldata, 13 | string calldata, 14 | bytes calldata 15 | ) external {} 16 | 17 | /// Returns true to always validate the contract call (nearly 0 gas). 18 | function validateContractCall( 19 | bytes32, 20 | string memory, 21 | string memory, 22 | bytes32 23 | ) external pure returns (bool) { 24 | return true; 25 | } 26 | 27 | function callContractMock( 28 | address destination, 29 | bytes calldata payload 30 | ) external { 31 | GlacisAxelarAdapter(destination).execute( 32 | 0, 33 | "Anvil", 34 | destination.toHexString(), 35 | payload 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /test/contracts/mocks/axelar/AxelarRetryGatewayMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | import {GlacisAxelarAdapter} from "../../../../contracts/adapters/GlacisAxelarAdapter.sol"; 4 | import {AddressString} from "../../../../contracts/libraries/AddressString.sol"; 5 | import {CheckSum} from "../../libraries/CheckSum.sol"; 6 | 7 | contract AxelarRetryGatewayMock { 8 | mapping(bytes32 => bool) public calledInstances; 9 | using AddressString for string; 10 | using CheckSum for address; 11 | 12 | /// A function that mocks IAxelarGateway, calling the msg.sender on the same chain 13 | /// much gas contract interactions take on the destination chain. 14 | /// Will fail the first time to simulate the possibility of a message being sent. 15 | function callContract( 16 | string calldata destinationChain, 17 | string calldata destinationAddress, 18 | bytes calldata payload 19 | ) external { 20 | bytes32 internalID = keccak256( 21 | abi.encode(destinationChain, destinationAddress, payload) 22 | ); 23 | if (calledInstances[internalID]) { 24 | bytes32 commandId = 0; 25 | 26 | // Get the contract address 27 | string memory contractAddrStr = destinationAddress[2:42]; // remove 0x 28 | address contractAddr = contractAddrStr.toAddress(); 29 | 30 | // For better GMP simulation, we use the checksummed address just like Axelar does. 31 | string memory fromAddr = string( 32 | abi.encodePacked("0x", msg.sender.getChecksum()) 33 | ); 34 | 35 | GlacisAxelarAdapter(contractAddr).execute( 36 | commandId, 37 | destinationChain, 38 | fromAddr, 39 | payload 40 | ); 41 | } else { 42 | calledInstances[internalID] = true; 43 | } 44 | } 45 | 46 | /// Returns true to always validate the contract call (nearly 0 gas). 47 | function validateContractCall( 48 | bytes32, 49 | string memory, 50 | string memory, 51 | bytes32 52 | ) external pure returns (bool) { 53 | return true; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /test/contracts/mocks/ccip/CCIPRouterMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ApacheV2 2 | pragma solidity 0.8.18; 3 | import {Client} from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol"; 4 | import {IAny2EVMMessageReceiver} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IAny2EVMMessageReceiver.sol"; 5 | 6 | contract CCIPRouterMock { 7 | uint256 public nonce; 8 | /// @notice Request a message to be sent to the destination chain 9 | /// @param message The cross-chain CCIP message including data and/or tokens 10 | /// @return messageId The message ID 11 | /// @dev Note if msg.value is larger than the required fee (from getFee) we accept 12 | /// the overpayment with no refund. 13 | /// @dev Reverts with appropriate reason upon invalid message. 14 | function ccipSend( 15 | uint64, // destinationChainSelector, 16 | Client.EVM2AnyMessage calldata message 17 | ) external payable returns (bytes32 messageId) { 18 | Client.EVMTokenAmount[] memory tokens = new Client.EVMTokenAmount[](0); 19 | nonce += 1; 20 | messageId = keccak256(abi.encode(message.data, nonce)); 21 | IAny2EVMMessageReceiver( 22 | address(abi.decode(message.receiver, (address))) 23 | ).ccipReceive( 24 | Client.Any2EVMMessage( 25 | messageId, 26 | uint64(block.chainid), 27 | abi.encode(msg.sender), 28 | message.data, 29 | tokens 30 | ) 31 | ); 32 | } 33 | 34 | function getFee( 35 | uint64, 36 | Client.EVM2AnyMessage calldata data 37 | ) external pure returns (uint256 fee) { 38 | bytes memory extraArgs = data.extraArgs; 39 | uint256 gasLimit; 40 | assembly { 41 | gasLimit := mload(add(extraArgs, 36)) 42 | } 43 | return gasLimit * 1000 + 21000; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /test/contracts/mocks/hyperlane/HyperlaneMailboxMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ApacheV2 2 | pragma solidity 0.8.18; 3 | 4 | import {Versioned} from "@hyperlane-xyz/core/contracts/upgrade/Versioned.sol"; 5 | import {TypeCasts} from "@hyperlane-xyz/core/contracts/libs/TypeCasts.sol"; 6 | import {Message} from "@hyperlane-xyz/core/contracts/libs/Message.sol"; 7 | import {IMessageRecipient} from "@hyperlane-xyz/core/contracts/interfaces/IMessageRecipient.sol"; 8 | import {IInterchainSecurityModule, ISpecifiesInterchainSecurityModule} from "@hyperlane-xyz/core/contracts/interfaces/IInterchainSecurityModule.sol"; 9 | import {Mailbox} from "@hyperlane-xyz/core/contracts/Mailbox.sol"; 10 | import {IPostDispatchHook} from "@hyperlane-xyz/core/contracts/interfaces/hooks/IPostDispatchHook.sol"; 11 | 12 | import {TestIsm} from "@hyperlane-xyz/core/contracts/test/TestIsm.sol"; 13 | import {TestPostDispatchHook} from "@hyperlane-xyz/core/contracts/test/TestPostDispatchHook.sol"; 14 | 15 | contract HyperlaneMailboxMock is Mailbox { 16 | using Message for bytes; 17 | 18 | uint32 public inboundUnprocessedNonce = 0; 19 | uint32 public inboundProcessedNonce = 0; 20 | 21 | mapping(uint32 => HyperlaneMailboxMock) public remoteMailboxes; 22 | mapping(uint256 => bytes) public inboundMessages; 23 | 24 | constructor() Mailbox(31337) { 25 | TestIsm ism = new TestIsm(); 26 | defaultIsm = ism; 27 | 28 | TestPostDispatchHook hook = new TestPostDispatchHook(); 29 | defaultHook = hook; 30 | requiredHook = hook; 31 | addRemoteMailbox(31337, this); 32 | _transferOwnership(msg.sender); 33 | _disableInitializers(); 34 | } 35 | 36 | function setHookFee(uint256 fee) external { 37 | TestPostDispatchHook(address(defaultHook)).setFee(fee); 38 | } 39 | 40 | function addRemoteMailbox( 41 | uint32 _domain, 42 | HyperlaneMailboxMock _mailbox 43 | ) internal { 44 | remoteMailboxes[_domain] = _mailbox; 45 | } 46 | 47 | function dispatch( 48 | uint32 destinationDomain, 49 | bytes32 recipientAddress, 50 | bytes calldata messageBody, 51 | bytes calldata metadata, 52 | IPostDispatchHook hook 53 | ) public payable override returns (bytes32) { 54 | bytes memory message = _buildMessage( 55 | destinationDomain, 56 | recipientAddress, 57 | messageBody 58 | ); 59 | bytes32 id = super.dispatch( 60 | destinationDomain, 61 | recipientAddress, 62 | messageBody, 63 | metadata, 64 | hook 65 | ); 66 | 67 | HyperlaneMailboxMock _destinationMailbox = remoteMailboxes[ 68 | destinationDomain 69 | ]; 70 | require( 71 | address(_destinationMailbox) != address(0), 72 | "Missing remote mailbox" 73 | ); 74 | _destinationMailbox.addInboundMessage(message); 75 | processNextInboundMessage(); 76 | 77 | return id; 78 | } 79 | 80 | function addInboundMessage(bytes calldata message) external { 81 | inboundMessages[inboundUnprocessedNonce] = message; 82 | inboundUnprocessedNonce++; 83 | } 84 | 85 | function processNextInboundMessage() public { 86 | bytes memory _message = inboundMessages[inboundProcessedNonce]; 87 | Mailbox(address(this)).process("", _message); 88 | inboundProcessedNonce++; 89 | } 90 | } -------------------------------------------------------------------------------- /test/contracts/mocks/lz/LayerZeroMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | import {GlacisLayerZeroAdapter} from "../../../../contracts/adapters/LayerZero/GlacisLayerZeroAdapter.sol"; 4 | 5 | contract LayerZeroGMPMock { 6 | error LayerZeroGMPMock__RefundAddressDoesNotReceiveRefund(); 7 | 8 | function send( 9 | uint16 _dstChainId, 10 | bytes calldata _destination, 11 | bytes calldata _payload, 12 | address _refundAddress, 13 | address, // _zroAddressPaymet 14 | bytes calldata // _adapaterParams 15 | ) external payable { 16 | // Destination = Source 17 | address destination = address(uint160(bytes20(_destination))); 18 | lzReceive( 19 | destination, 20 | _dstChainId, 21 | abi.encodePacked(msg.sender, destination), // This is sending the entire route 0xADDRESSADDRESS 22 | 0, 23 | _payload 24 | ); 25 | (bool success, ) = payable(_refundAddress).call{value: msg.value}(""); 26 | if (!success) { 27 | revert LayerZeroGMPMock__RefundAddressDoesNotReceiveRefund(); 28 | } 29 | } 30 | 31 | function lzReceive( 32 | address destination, 33 | uint16 srcChainId, 34 | bytes memory srcAddress, // srcAddress, will be the other adapter 35 | uint64 value, 36 | bytes memory payload 37 | ) public { 38 | GlacisLayerZeroAdapter(destination).lzReceive( 39 | srcChainId, 40 | srcAddress, 41 | value, 42 | payload 43 | ); 44 | } 45 | 46 | function getChainId() external pure returns (uint16) { 47 | return 1; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /test/contracts/mocks/lz/LayerZeroOneWayMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | import {GlacisLayerZeroAdapter} from "../../../../contracts/adapters/LayerZero/GlacisLayerZeroAdapter.sol"; 4 | 5 | contract LayerZeroOneWayMock { 6 | function send( 7 | uint16 _dstChainId, 8 | bytes calldata _destination, 9 | bytes calldata _payload, 10 | address, // _refundAddress 11 | address, // _zroAddressPaymet 12 | bytes calldata // _adapaterParams 13 | ) external payable {} 14 | 15 | function getChainId() external pure returns (uint16) { 16 | return 1; 17 | } 18 | 19 | function send_mock( 20 | uint16 _dstChainId, 21 | bytes calldata _destination, 22 | bytes calldata _payload, 23 | address, // _refundAddress 24 | address, // _zroAddressPaymet 25 | bytes calldata // _adapaterParams 26 | ) external payable { 27 | GlacisLayerZeroAdapter(msg.sender).lzReceive( 28 | _dstChainId, 29 | _destination, 30 | 0, 31 | _payload 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test/contracts/mocks/lz/LayerZeroV2Mock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | import {GlacisLayerZeroV2Adapter} from "../../../../contracts/adapters/LayerZero/GlacisLayerZeroV2Adapter.sol"; 4 | import {AddressBytes32} from "../../../../contracts/libraries/AddressBytes32.sol"; 5 | import {MessagingParams, MessagingReceipt, Origin, MessagingFee} from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol"; 6 | 7 | contract LayerZeroV2Mock { 8 | using AddressBytes32 for address; 9 | using AddressBytes32 for bytes32; 10 | 11 | error LayerZeroV2GMPMock__RefundAddressDoesNotReceiveRefund(); 12 | 13 | uint64 private nonce = 0; 14 | mapping(address => address) delegates; 15 | 16 | function send( 17 | MessagingParams calldata _params, 18 | address _refundAddress 19 | ) external payable returns (MessagingReceipt memory) { 20 | bytes32 messageId = keccak256(abi.encode(_params, nonce)); 21 | 22 | GlacisLayerZeroV2Adapter(_params.receiver.toAddress()).lzReceive( 23 | Origin(getChainId(), msg.sender.toBytes32(), nonce), 24 | keccak256(abi.encode(_params, nonce)), 25 | _params.message, 26 | msg.sender, 27 | "" 28 | ); 29 | 30 | nonce += 1; 31 | 32 | (bool success, ) = payable(_refundAddress).call{value: msg.value}(""); 33 | if (!success) { 34 | revert LayerZeroV2GMPMock__RefundAddressDoesNotReceiveRefund(); 35 | } 36 | 37 | return MessagingReceipt(messageId, nonce - 1, MessagingFee(msg.value, 0)); 38 | } 39 | 40 | function getChainId() public pure returns (uint32) { 41 | return 31337; 42 | } 43 | 44 | function setDelegate(address delegate) external { 45 | delegates[msg.sender] = delegate; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /test/contracts/mocks/wormhole/WormholeRelayerMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | import {IWormholeReceiver} from "../../../../contracts/adapters/Wormhole/IWormholeReceiver.sol"; 5 | import {IWormholeRelayer, VaaKey} from "../../../../contracts/adapters/Wormhole/IWormholeRelayer.sol"; 6 | 7 | import {GlacisWormholeAdapter} from "../../../../contracts/adapters/Wormhole/GlacisWormholeAdapter.sol"; 8 | 9 | contract WormholeRelayerMock is IWormholeRelayer, IWormholeReceiver { 10 | function sendPayloadToEvm( 11 | uint16 targetChain, 12 | address targetAddress, 13 | bytes memory payload, 14 | uint256 receiverValue, 15 | uint256 gasLimit 16 | ) public payable returns (uint64) { 17 | // Execute on the same chain 18 | receiveWormholeMessages( 19 | payload, 20 | new bytes[](0), 21 | bytes32(bytes20(msg.sender)) >> 96, 22 | targetChain, 23 | // Mock a hash 24 | keccak256( 25 | abi.encode( 26 | targetChain, 27 | targetAddress, 28 | payload, 29 | receiverValue, 30 | gasLimit, 31 | block.timestamp 32 | ) 33 | ) 34 | ); 35 | return 0; 36 | } 37 | 38 | function receiveWormholeMessages( 39 | bytes memory payload, 40 | bytes[] memory, // additionalVaas, 41 | bytes32 sourceAddress, 42 | uint16 sourceChain, 43 | bytes32 deliveryHash 44 | ) public payable override { 45 | IWormholeReceiver(address(uint160(uint256(sourceAddress)))) 46 | .receiveWormholeMessages( 47 | payload, 48 | new bytes[](0), 49 | sourceAddress, 50 | sourceChain, 51 | deliveryHash 52 | ); 53 | } 54 | 55 | function sendPayloadToEvm( 56 | uint16 targetChain, 57 | address targetAddress, 58 | bytes memory payload, 59 | uint256 receiverValue, 60 | uint256 gasLimit, 61 | uint16, 62 | address 63 | ) external payable returns (uint64) { 64 | return 65 | sendPayloadToEvm( 66 | targetChain, 67 | targetAddress, 68 | payload, 69 | receiverValue, 70 | gasLimit 71 | ); 72 | } 73 | 74 | function deliver( 75 | bytes[] memory encodedVMs, 76 | bytes memory encodedDeliveryVAA, 77 | address payable relayerRefundAddress, 78 | bytes memory deliveryOverrides 79 | ) external payable {} 80 | 81 | function getDefaultDeliveryProvider() 82 | external 83 | view 84 | returns (address deliveryProvider) 85 | {} 86 | 87 | function quoteDeliveryPrice( 88 | uint16 targetChain, 89 | uint256 receiverValue, 90 | bytes memory encodedExecutionParameters, 91 | address deliveryProviderAddress 92 | ) 93 | external 94 | view 95 | returns (uint256 nativePriceQuote, bytes memory encodedExecutionInfo) 96 | {} 97 | 98 | function quoteEVMDeliveryPrice( 99 | uint16 targetChain, 100 | uint256 receiverValue, 101 | uint256 gasLimit, 102 | address deliveryProviderAddress 103 | ) 104 | external 105 | view 106 | returns ( 107 | uint256 nativePriceQuote, 108 | uint256 targetChainRefundPerGasUnused 109 | ) 110 | {} 111 | 112 | function quoteNativeForChain( 113 | uint16 targetChain, 114 | uint256 currentChainAmount, 115 | address deliveryProviderAddress 116 | ) external view returns (uint256 targetChainAmount) {} 117 | 118 | function getRegisteredWormholeRelayerContract( 119 | uint16 chainId 120 | ) external view returns (bytes32) {} 121 | 122 | function quoteEVMDeliveryPrice( 123 | uint16 targetChain, 124 | uint256 receiverValue, 125 | uint256 gasLimit 126 | ) 127 | external 128 | view 129 | returns ( 130 | uint256 nativePriceQuote, 131 | uint256 targetChainRefundPerGasUnused 132 | ) 133 | {} 134 | 135 | function resend( 136 | VaaKey memory deliveryVaaKey, 137 | uint16 targetChain, 138 | uint256 newReceiverValue, 139 | bytes memory newEncodedExecutionParameters, 140 | address newDeliveryProviderAddress 141 | ) external payable returns (uint64 sequence) {} 142 | 143 | function sendVaasToEvm( 144 | uint16 targetChain, 145 | address targetAddress, 146 | bytes memory payload, 147 | uint256 receiverValue, 148 | uint256 gasLimit, 149 | VaaKey[] memory vaaKeys, 150 | uint16 refundChain, 151 | address refundAddress 152 | ) external payable returns (uint64 sequence) {} 153 | 154 | function sendVaasToEvm( 155 | uint16 targetChain, 156 | address targetAddress, 157 | bytes memory payload, 158 | uint256 receiverValue, 159 | uint256 gasLimit, 160 | VaaKey[] memory vaaKeys 161 | ) external payable returns (uint64 sequence) {} 162 | 163 | function send( 164 | uint16 targetChain, 165 | bytes32 targetAddress, 166 | bytes memory payload, 167 | uint256 receiverValue, 168 | uint256 paymentForExtraReceiverValue, 169 | bytes memory encodedExecutionParameters, 170 | uint16 refundChain, 171 | bytes32 refundAddress, 172 | address deliveryProviderAddress, 173 | VaaKey[] memory vaaKeys, 174 | uint8 consistencyLevel 175 | ) external payable returns (uint64 sequence) {} 176 | 177 | function resendToEvm( 178 | VaaKey memory deliveryVaaKey, 179 | uint16 targetChain, 180 | uint256 newReceiverValue, 181 | uint256 newGasLimit, 182 | address newDeliveryProviderAddress 183 | ) external payable returns (uint64 sequence) {} 184 | 185 | function sendToEvm( 186 | uint16 targetChain, 187 | address targetAddress, 188 | bytes memory payload, 189 | uint256 receiverValue, 190 | uint256 paymentForExtraReceiverValue, 191 | uint256 gasLimit, 192 | uint16 refundChain, 193 | address refundAddress, 194 | address deliveryProviderAddress, 195 | VaaKey[] memory vaaKeys, 196 | uint8 consistencyLevel 197 | ) external payable returns (uint64 sequence) {} 198 | } 199 | -------------------------------------------------------------------------------- /test/contracts/samples/CustomAdapterSample.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity 0.8.18; 4 | 5 | import {GlacisAbstractAdapter} from "../../../contracts/adapters/GlacisAbstractAdapter.sol"; 6 | import {IGlacisRouter} from "../../../contracts/routers/GlacisRouter.sol"; 7 | import {GlacisCommons} from "../../../contracts/commons/GlacisCommons.sol"; 8 | 9 | contract CustomAdapterSample is GlacisAbstractAdapter { 10 | constructor( 11 | address glacisRouter_, 12 | address owner_ 13 | ) GlacisAbstractAdapter(IGlacisRouter(glacisRouter_), owner_) {} 14 | 15 | 16 | function chainIsAvailable(uint256) public view virtual returns (bool) { 17 | return true; 18 | } 19 | 20 | function _sendMessage( 21 | uint256 toChainId, 22 | address, 23 | GlacisCommons.CrossChainGas memory, 24 | bytes memory payload 25 | ) internal override onlyGlacisRouter { 26 | GLACIS_ROUTER.receiveMessage(toChainId, payload); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /test/contracts/samples/GlacisClientSample.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | import {GlacisClientOwnable} from "../../../contracts/client/GlacisClientOwnable.sol"; 4 | import {GlacisCommons} from "../../../contracts/commons/GlacisCommons.sol"; 5 | 6 | contract GlacisClientSample is GlacisClientOwnable { 7 | uint256 public value; 8 | 9 | constructor( 10 | address glacisRouter_, 11 | address owner_ 12 | ) GlacisClientOwnable(glacisRouter_, 1, owner_) {} 13 | 14 | function setRemoteValue__execute( 15 | uint256 toChainId, 16 | bytes32 to, 17 | address adapter, 18 | bytes calldata payload 19 | ) external payable returns (bytes32) { 20 | return _routeSingle(toChainId, to, payload, adapter, msg.sender, msg.value); 21 | } 22 | 23 | function setRemoteValue__redundancy( 24 | uint256 toChainId, 25 | bytes32 to, 26 | address[] memory adapters, 27 | CrossChainGas[] memory fees, 28 | bytes calldata payload 29 | ) external payable returns (bytes32) { 30 | return 31 | _routeRedundant( 32 | toChainId, 33 | to, 34 | payload, 35 | adapters, 36 | fees, 37 | msg.sender, 38 | msg.value 39 | ); 40 | } 41 | 42 | function setRemoteValue__retryable( 43 | uint256 chainId, 44 | bytes32 to, 45 | address[] memory adapters, 46 | CrossChainGas[] memory fees, 47 | bytes memory payload 48 | ) external payable returns (bytes32,uint256) { 49 | return 50 | _route( 51 | chainId, 52 | to, 53 | payload, 54 | adapters, 55 | fees, 56 | msg.sender, 57 | true, 58 | msg.value 59 | ); 60 | } 61 | 62 | function setRemoteValue( 63 | uint256 chainId, 64 | bytes32 to, 65 | bytes memory payload, 66 | address[] memory adapters, 67 | CrossChainGas[] memory fees, 68 | address refundAddress, 69 | bool retryable, 70 | uint256 gasPayment 71 | ) external payable returns (bytes32,uint256 ) { 72 | return 73 | _route( 74 | chainId, 75 | to, 76 | payload, 77 | adapters, 78 | fees, 79 | refundAddress, 80 | retryable, 81 | gasPayment 82 | ); 83 | } 84 | 85 | function setRemoteValue__retry( 86 | uint256 chainId, 87 | bytes32 to, 88 | address[] memory adapters, 89 | CrossChainGas[] memory fees, 90 | bytes memory payload, 91 | bytes32 messageId, 92 | uint256 nonce 93 | ) external payable returns (bytes32) { 94 | return 95 | _retryRoute( 96 | chainId, 97 | to, 98 | payload, 99 | adapters, 100 | fees, 101 | msg.sender, 102 | messageId, 103 | nonce, 104 | msg.value 105 | ); 106 | } 107 | 108 | event ValueChanged(uint256 indexed value); 109 | 110 | function _receiveMessage( 111 | address[] memory, // fromGmpId, 112 | uint256, // fromChainId, 113 | bytes32, // fromAddress, 114 | bytes memory payload 115 | ) internal override { 116 | // NOTE: changed += to test for redundant messages 117 | if (payload.length > 0) (value) += abi.decode(payload, (uint256)); 118 | } 119 | 120 | // Setup of custom quorum (for testing purposes) 121 | 122 | uint256 internal customQuorum = 1; 123 | 124 | function setQuorum(uint256 q) external onlyOwner { 125 | customQuorum = q; 126 | } 127 | 128 | function getQuorum( 129 | GlacisCommons.GlacisData memory, 130 | bytes memory, 131 | uint256 132 | ) public view override returns (uint256) { 133 | return customQuorum; 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /test/contracts/samples/GlacisClientTextSample.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | import {GlacisClientOwnable} from "../../../contracts/client/GlacisClientOwnable.sol"; 4 | import {GlacisCommons} from "../../../contracts/commons/GlacisCommons.sol"; 5 | 6 | contract GlacisClientTextSample is GlacisClientOwnable { 7 | string public value; 8 | 9 | constructor( 10 | address glacisRouter_, 11 | address owner_ 12 | ) GlacisClientOwnable(glacisRouter_, 0, owner_) {} 13 | 14 | function setRemoteValue__execute( 15 | uint256 toChainId, 16 | bytes32 to, 17 | address adapter, 18 | bytes calldata payload 19 | ) external payable returns (bytes32) { 20 | return _routeSingle(toChainId, to, payload, adapter, msg.sender, msg.value); 21 | } 22 | 23 | function setRemoteValue__redundancy( 24 | uint256 toChainId, 25 | bytes32 to, 26 | address[] memory adapters, 27 | CrossChainGas[] memory fees, 28 | bytes calldata payload 29 | ) external payable returns (bytes32) { 30 | return 31 | _routeRedundant( 32 | toChainId, 33 | to, 34 | payload, 35 | adapters, 36 | fees, 37 | msg.sender, 38 | msg.value 39 | ); 40 | } 41 | 42 | function setRemoteValue__retryable( 43 | uint256 chainId, 44 | bytes32 to, 45 | address[] memory adapters, 46 | CrossChainGas[] memory fees, 47 | bytes memory payload 48 | ) external payable returns (bytes32,uint256) { 49 | return 50 | _route( 51 | chainId, 52 | to, 53 | payload, 54 | adapters, 55 | fees, 56 | msg.sender, 57 | true, 58 | msg.value 59 | ); 60 | } 61 | 62 | function setRemoteValue( 63 | uint256 chainId, 64 | bytes32 to, 65 | bytes memory payload, 66 | address[] memory adapters, 67 | CrossChainGas[] memory fees, 68 | address refundAddress, 69 | bool retryable, 70 | uint256 gasPayment 71 | ) external payable returns (bytes32,uint256) { 72 | return 73 | _route( 74 | chainId, 75 | to, 76 | payload, 77 | adapters, 78 | fees, 79 | refundAddress, 80 | retryable, 81 | gasPayment 82 | ); 83 | } 84 | 85 | function setRemoteValue__retry( 86 | uint256 chainId, 87 | bytes32 to, 88 | address[] memory adapters, 89 | CrossChainGas[] memory fees, 90 | bytes memory payload, 91 | bytes32 messageId, 92 | uint256 nonce 93 | ) external payable returns (bytes32) { 94 | return 95 | _retryRoute( 96 | chainId, 97 | to, 98 | payload, 99 | adapters, 100 | fees, 101 | msg.sender, 102 | messageId, 103 | nonce, 104 | msg.value 105 | ); 106 | } 107 | 108 | function _receiveMessage( 109 | address[] memory, // fromGmpId, 110 | uint256, // fromChainId, 111 | bytes32, // fromAddress, 112 | bytes memory payload 113 | ) internal override { 114 | (value) = abi.decode(payload, (string)); 115 | } 116 | 117 | // Setup of custom quorum (for testing purposes) 118 | 119 | uint256 internal customQuorum = 1; 120 | 121 | function setQuorum(uint256 q) external onlyOwner { 122 | customQuorum = q; 123 | } 124 | 125 | function getQuorum( 126 | GlacisCommons.GlacisData memory, 127 | bytes memory, 128 | uint256 129 | ) public view override returns (uint256) { 130 | return customQuorum; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /test/contracts/samples/GlacisTokenClientSampleDestination.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | import {GlacisTokenClientOwnable} from "../../../contracts/client/GlacisTokenClientOwnable.sol"; 4 | import {GlacisCommons} from "../../../contracts/commons/GlacisCommons.sol"; 5 | import {XERC20} from "../../../contracts/token/XERC20.sol"; 6 | import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 7 | 8 | contract GlacisTokenClientSampleDestination is GlacisTokenClientOwnable { 9 | uint256 public value; 10 | 11 | receive() external payable {} 12 | 13 | constructor( 14 | address XERC20Sample_, 15 | address ERC20Sample_, 16 | address XERC20LockboxSample_, 17 | address glacisTokenMediator_, 18 | address glacisRouter_, 19 | address owner_ 20 | ) GlacisTokenClientOwnable(glacisTokenMediator_, glacisRouter_, 0, owner_) { 21 | XERC20(XERC20Sample_).approve(glacisTokenMediator_, 10e18); 22 | ERC20(ERC20Sample_).approve(XERC20LockboxSample_, 10e18); 23 | } 24 | 25 | event ValueChanged(uint256 indexed value); 26 | 27 | function _receiveMessageWithTokens( 28 | address[] memory, // fromAdapters, 29 | uint256, // fromChainId, 30 | bytes32, // fromAddress, 31 | bytes memory payload, 32 | address, // token, 33 | uint256 // amount 34 | ) internal override { 35 | if (payload.length > 0) (value) += abi.decode(payload, (uint256)); 36 | emit ValueChanged(value); 37 | } 38 | 39 | // Setup of custom quorum (for testing purposes) 40 | 41 | uint256 internal customQuorum = 1; 42 | 43 | function setQuorum(uint256 q) external onlyOwner { 44 | customQuorum = q; 45 | } 46 | 47 | function getQuorum( 48 | GlacisCommons.GlacisData memory, 49 | bytes memory, 50 | uint256 51 | ) public view override returns (uint256) { 52 | return customQuorum; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /test/contracts/samples/GlacisTokenClientSampleSource.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | import {GlacisTokenClientOwnable} from "../../../contracts/client/GlacisTokenClientOwnable.sol"; 4 | import {GlacisCommons} from "../../../contracts/commons/GlacisCommons.sol"; 5 | import {XERC20} from "../../../contracts/token/XERC20.sol"; 6 | import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 7 | 8 | contract GlacisTokenClientSampleSource is GlacisTokenClientOwnable { 9 | uint256 public value; 10 | 11 | receive() external payable {} 12 | 13 | constructor( 14 | address XERC20Sample_, 15 | address ERC20Sample_, 16 | address XERC20LockboxSample_, 17 | address glacisTokenMediator_, 18 | address glacisRouter_, 19 | address owner_ 20 | ) GlacisTokenClientOwnable(glacisTokenMediator_, glacisRouter_, 0, owner_) { 21 | XERC20(XERC20Sample_).approve(glacisTokenMediator_, 10e18); 22 | ERC20(ERC20Sample_).approve(XERC20LockboxSample_, 10e18); 23 | } 24 | 25 | function sendMessageAndTokens__abstract( 26 | uint256 toChainId, 27 | bytes32 to, 28 | address adapter, 29 | bytes memory payload, 30 | address token, 31 | uint256 amount 32 | ) external payable returns (bytes32,uint256) { 33 | return 34 | _routeWithTokensSingle( 35 | toChainId, 36 | to, 37 | payload, 38 | adapter, 39 | msg.sender, 40 | token, 41 | amount, 42 | msg.value 43 | ); 44 | } 45 | 46 | function sendMessageAndTokens__redundant( 47 | uint256 toChainId, 48 | bytes32 to, 49 | address[] memory adapters, 50 | CrossChainGas[] memory fees, 51 | bytes memory payload, 52 | address token, 53 | uint256 amount 54 | ) external payable returns (bytes32,uint256) { 55 | return 56 | _routeWithTokensRedundant( 57 | toChainId, 58 | to, 59 | payload, 60 | adapters, 61 | fees, 62 | msg.sender, 63 | token, 64 | amount, 65 | msg.value 66 | ); 67 | } 68 | 69 | function sendMessageAndTokens( 70 | uint256 chainId, 71 | bytes32 to, 72 | address[] calldata adapters, 73 | CrossChainGas[] calldata fees, 74 | bytes calldata payload, 75 | address token, 76 | uint256 amount 77 | ) external payable returns (bytes32,uint256) { 78 | return 79 | _routeWithTokens( 80 | chainId, 81 | to, 82 | payload, 83 | adapters, 84 | fees, 85 | msg.sender, 86 | token, 87 | amount, 88 | msg.value 89 | ); 90 | } 91 | 92 | RetrySendWithTokenPackage public package; 93 | 94 | // @notice Struct for sending tokens as a retry. Required to avoid stack too deep. 95 | struct RetrySendWithTokenPackage { 96 | address token; 97 | uint256 amount; 98 | bytes32 messageId; 99 | uint256 nonce; 100 | } 101 | 102 | function createRetryWithTokenPackage( 103 | address _token, 104 | uint256 _amount, 105 | bytes32 _messageId, 106 | uint256 _nonce 107 | ) public { 108 | package = RetrySendWithTokenPackage({ 109 | token: _token, 110 | amount: _amount, 111 | messageId: _messageId, 112 | nonce: _nonce 113 | }); 114 | } 115 | 116 | // Función para obtener los detalles del paquete 117 | function getRetryWithTokenPackage() 118 | public 119 | view 120 | returns (RetrySendWithTokenPackage memory) 121 | { 122 | return package; 123 | } 124 | 125 | function retrySendWithTokens( 126 | uint256 chainId, 127 | bytes32 to, 128 | address[] calldata adapters, 129 | CrossChainGas[] calldata fees, 130 | bytes calldata payload, 131 | RetrySendWithTokenPackage calldata _package 132 | ) external payable returns (bytes32) { 133 | return 134 | _retryRouteWithTokens( 135 | chainId, 136 | to, 137 | payload, 138 | adapters, 139 | fees, 140 | msg.sender, 141 | _package.messageId, 142 | _package.nonce, 143 | _package.token, 144 | _package.amount, 145 | msg.value 146 | ); 147 | } 148 | 149 | event ValueChanged(uint256 indexed value); 150 | 151 | function _receiveMessageWithTokens( 152 | address[] memory, // fromAdapters, 153 | uint256, // fromChainId, 154 | bytes32, // fromAddress, 155 | bytes memory payload, 156 | address, // token, 157 | uint256 // amount 158 | ) internal override { 159 | if (payload.length > 0) (value) += abi.decode(payload, (uint256)); 160 | emit ValueChanged(value); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /test/contracts/samples/control/AxelarSample.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity 0.8.18; 4 | 5 | import {AxelarExecutable} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/executable/AxelarExecutable.sol"; 6 | import {IAxelarGasService} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGasService.sol"; 7 | import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; 8 | 9 | contract AxelarSample is AxelarExecutable { 10 | uint256 public value; 11 | 12 | IAxelarGasService public immutable GAS_SERVICE; 13 | 14 | // event Sent(string, string, string); 15 | // event Received(string, string, bytes); 16 | 17 | // Using this library because it is abnormal to use strings and will likely 18 | // be used by other developers. 19 | using Strings for address; 20 | 21 | constructor( 22 | address gateway_, 23 | address gasReceiver_ 24 | ) AxelarExecutable(gateway_) { 25 | GAS_SERVICE = IAxelarGasService(gasReceiver_); 26 | } 27 | 28 | // Call this function to update the value of this contract along with all its siblings'. 29 | function setRemoteValue( 30 | string calldata destinationChain, 31 | address destinationAddress, 32 | bytes memory payload 33 | ) external payable { 34 | string memory addr = destinationAddress.toHexString(); 35 | 36 | GAS_SERVICE.payNativeGasForContractCall{value: msg.value}( 37 | address(this), 38 | destinationChain, 39 | addr, 40 | payload, 41 | msg.sender 42 | ); 43 | 44 | gateway.callContract(destinationChain, addr, payload); 45 | // emit Sent(destinationAddress, destinationChain, value_); 46 | } 47 | 48 | // Handles calls created by setAndSend. Updates this contract's value 49 | function _execute( 50 | string calldata, 51 | string calldata, 52 | bytes calldata payload 53 | ) internal override { 54 | if (payload.length > 0) (value) += abi.decode(payload, (uint256)); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /test/contracts/samples/control/AxelarTextSample.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity 0.8.18; 4 | 5 | import {AxelarExecutable} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/executable/AxelarExecutable.sol"; 6 | import {IAxelarGasService} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGasService.sol"; 7 | import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; 8 | 9 | contract AxelarTextSample is AxelarExecutable { 10 | string public value; 11 | 12 | IAxelarGasService public immutable GAS_SERVICE; 13 | 14 | // Using this library because it is abnormal to use strings and will likely 15 | // be used by other developers. 16 | using Strings for address; 17 | 18 | constructor( 19 | address gateway_, 20 | address gasReceiver_ 21 | ) AxelarExecutable(gateway_) { 22 | GAS_SERVICE = IAxelarGasService(gasReceiver_); 23 | } 24 | 25 | // Call this function to update the value of this contract along with all its siblings'. 26 | function setRemoteValue( 27 | string calldata destinationChain, 28 | address destinationAddress, 29 | bytes memory payload 30 | ) external payable { 31 | string memory addr = destinationAddress.toHexString(); 32 | 33 | GAS_SERVICE.payNativeGasForContractCall{value: msg.value}( 34 | address(this), 35 | destinationChain, 36 | addr, 37 | payload, 38 | msg.sender 39 | ); 40 | 41 | gateway.callContract(destinationChain, addr, payload); 42 | } 43 | 44 | // Handles calls created by setAndSend. Updates this contract's value 45 | function _execute( 46 | string calldata, 47 | string calldata, 48 | bytes calldata payload_ 49 | ) internal override { 50 | value = abi.decode(payload_, (string)); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /test/contracts/samples/control/CCIPSample.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity 0.8.18; 4 | import {Client} from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol"; 5 | import {CCIPReceiver} from "@chainlink/contracts-ccip/src/v0.8/ccip/applications/CCIPReceiver.sol"; 6 | import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol"; 7 | 8 | contract CCIPSample is CCIPReceiver { 9 | uint256 public value; 10 | 11 | constructor(address ccipRouter_) CCIPReceiver(ccipRouter_) {} 12 | 13 | function setRemoteValue( 14 | uint64 destinationChainId, 15 | address destinationAddress, 16 | bytes memory payload 17 | ) external payable { 18 | Client.EVM2AnyMessage memory evm2AnyMessage = Client.EVM2AnyMessage({ 19 | receiver: abi.encode(destinationAddress), 20 | data: payload, 21 | tokenAmounts: new Client.EVMTokenAmount[](0), 22 | extraArgs: Client._argsToBytes( 23 | Client.EVMExtraArgsV1({gasLimit: 1_000_000}) 24 | ), 25 | feeToken: address(0) 26 | }); 27 | 28 | IRouterClient router = IRouterClient(this.getRouter()); 29 | 30 | // Get the fee required to send the CCIP message 31 | uint256 fees = router.getFee(destinationChainId, evm2AnyMessage); 32 | 33 | // Send the CCIP message through the router and store the returned CCIP message ID 34 | router.ccipSend{value: fees}(destinationChainId, evm2AnyMessage); 35 | } 36 | 37 | function _ccipReceive( 38 | Client.Any2EVMMessage memory any2EvmMessage 39 | ) internal override { 40 | if (any2EvmMessage.data.length > 0) 41 | (value) += abi.decode(any2EvmMessage.data, (uint256)); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /test/contracts/samples/control/CCIPTextSample.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity 0.8.18; 4 | import {Client} from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol"; 5 | import {CCIPReceiver} from "@chainlink/contracts-ccip/src/v0.8/ccip/applications/CCIPReceiver.sol"; 6 | import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol"; 7 | 8 | contract CCIPTextSample is CCIPReceiver { 9 | string public value; 10 | 11 | constructor(address ccipRouter_) CCIPReceiver(ccipRouter_) {} 12 | 13 | function setRemoteValue( 14 | uint64 destinationChainId, 15 | address destinationAddress, 16 | bytes memory payload 17 | ) external payable { 18 | Client.EVM2AnyMessage memory evm2AnyMessage = Client.EVM2AnyMessage({ 19 | receiver: abi.encode(destinationAddress), 20 | data: payload, 21 | tokenAmounts: new Client.EVMTokenAmount[](0), 22 | extraArgs: Client._argsToBytes( 23 | Client.EVMExtraArgsV1({gasLimit: 1_000_000}) 24 | ), 25 | feeToken: address(0) 26 | }); 27 | 28 | IRouterClient router = IRouterClient(this.getRouter()); 29 | 30 | // Get the fee required to send the CCIP message 31 | uint256 fees = router.getFee(destinationChainId, evm2AnyMessage); 32 | 33 | // Send the CCIP message through the router and store the returned CCIP message ID 34 | router.ccipSend{value: fees}(destinationChainId, evm2AnyMessage); 35 | } 36 | 37 | function _ccipReceive( 38 | Client.Any2EVMMessage memory any2EvmMessage 39 | ) internal override { 40 | value = abi.decode(any2EvmMessage.data, (string)); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /test/contracts/samples/control/HyperlaneSample.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity 0.8.18; 4 | import {IMailbox} from "@hyperlane-xyz/core/contracts/interfaces/IMailbox.sol"; 5 | import {StandardHookMetadata} from "@hyperlane-xyz/core/contracts/hooks/libs/StandardHookMetadata.sol"; 6 | import {TypeCasts} from "@hyperlane-xyz/core/contracts/libs/TypeCasts.sol"; 7 | import {IInterchainSecurityModule} from "@hyperlane-xyz/core/contracts/interfaces/IInterchainSecurityModule.sol"; 8 | import {IPostDispatchHook} from "@hyperlane-xyz/core/contracts/interfaces/hooks/IPostDispatchHook.sol"; 9 | 10 | contract HyperlaneSample { 11 | uint256 public value; 12 | 13 | IInterchainSecurityModule public interchainSecurityModule; 14 | IMailbox public immutable MAIL_BOX; 15 | uint32 public immutable LOCAL_DOMAIN; 16 | 17 | constructor( 18 | address hyperlaneMailbox_ 19 | ) { 20 | MAIL_BOX = IMailbox(hyperlaneMailbox_); 21 | LOCAL_DOMAIN = MAIL_BOX.localDomain(); 22 | } 23 | 24 | function setRemoteValue( 25 | uint16 destinationChainId, 26 | address destinationAddress, 27 | bytes calldata payload 28 | ) external payable { 29 | // Send message across chains 30 | MAIL_BOX.dispatch{value: 1}( 31 | destinationChainId, 32 | bytes32(uint256(uint160(destinationAddress))), 33 | payload 34 | ); 35 | } 36 | 37 | function handle( 38 | uint32, //_origin 39 | bytes32, // _sender 40 | bytes calldata _message 41 | ) 42 | external 43 | payable 44 | { 45 | if (_message.length > 0) (value) += abi.decode(_message, (uint256)); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /test/contracts/samples/control/HyperlaneTextSample.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity 0.8.18; 4 | import {IMailbox} from "@hyperlane-xyz/core/contracts/interfaces/IMailbox.sol"; 5 | import {StandardHookMetadata} from "@hyperlane-xyz/core/contracts/hooks/libs/StandardHookMetadata.sol"; 6 | import {TypeCasts} from "@hyperlane-xyz/core/contracts/libs/TypeCasts.sol"; 7 | import {IInterchainSecurityModule} from "@hyperlane-xyz/core/contracts/interfaces/IInterchainSecurityModule.sol"; 8 | import {IPostDispatchHook} from "@hyperlane-xyz/core/contracts/interfaces/hooks/IPostDispatchHook.sol"; 9 | 10 | contract HyperlaneTextSample { 11 | string public value; 12 | 13 | IMailbox public immutable MAIL_BOX; 14 | uint32 public immutable LOCAL_DOMAIN; 15 | 16 | constructor( 17 | address hyperlaneMailbox_ 18 | ) { 19 | MAIL_BOX = IMailbox(hyperlaneMailbox_); 20 | LOCAL_DOMAIN = MAIL_BOX.localDomain(); 21 | } 22 | 23 | function setRemoteValue( 24 | uint16 destinationChainId, 25 | address destinationAddress, 26 | bytes calldata payload 27 | ) external payable { 28 | // Send message across chains 29 | MAIL_BOX.dispatch{value: 1}( 30 | destinationChainId, 31 | bytes32(uint256(uint160(destinationAddress))), 32 | payload 33 | ); 34 | } 35 | 36 | function handle( 37 | uint32, //_origin 38 | bytes32, // _sender 39 | bytes calldata _message 40 | ) 41 | external 42 | payable 43 | { 44 | value = abi.decode(_message, (string)); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /test/contracts/samples/control/LayerZeroSample.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | import {SimpleNonblockingLzApp} from "contracts/adapters/LayerZero/SimpleNonblockingLzApp.sol"; 5 | 6 | contract LayerZeroSample is SimpleNonblockingLzApp { 7 | uint256 public value; 8 | 9 | constructor(address _lzEndpoint) SimpleNonblockingLzApp(_lzEndpoint) {} 10 | 11 | function setRemoteValue( 12 | uint16 destChainId, 13 | address destChainAddress, 14 | bytes memory payload 15 | ) external payable { 16 | _lzSend({ 17 | _dstChainId: destChainId, 18 | _dstChainAddress: destChainAddress, 19 | _payload: payload, 20 | _refundAddress: payable(msg.sender), 21 | _zroPaymentAddress: address(0x0), 22 | _adapterParams: bytes(""), 23 | _nativeFee: msg.value 24 | }); 25 | } 26 | 27 | function _nonblockingLzReceive( 28 | uint16, 29 | bytes memory, 30 | uint64, 31 | bytes memory payload 32 | ) internal override { 33 | if (payload.length > 0) (value) += abi.decode(payload, (uint256)); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /test/contracts/samples/control/LayerZeroTextSample.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | import {SimpleNonblockingLzApp} from "contracts/adapters/LayerZero/SimpleNonblockingLzApp.sol"; 5 | 6 | contract LayerZeroTextSample is SimpleNonblockingLzApp { 7 | string public value; 8 | 9 | constructor(address _lzEndpoint) SimpleNonblockingLzApp(_lzEndpoint) {} 10 | 11 | function setRemoteValue( 12 | uint16 destChainId, 13 | address destChainAddress, 14 | bytes memory payload 15 | ) external payable { 16 | _lzSend({ 17 | _dstChainId: destChainId, 18 | _dstChainAddress: destChainAddress, 19 | _payload: payload, 20 | _refundAddress: payable(msg.sender), 21 | _zroPaymentAddress: address(0x0), 22 | _adapterParams: bytes(""), 23 | _nativeFee: msg.value 24 | }); 25 | } 26 | 27 | function _nonblockingLzReceive( 28 | uint16, 29 | bytes memory, 30 | uint64, 31 | bytes memory _payload 32 | ) internal override { 33 | value = abi.decode(_payload, (string)); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /test/contracts/samples/control/WormholeSample.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity 0.8.18; 4 | import {IWormholeRelayer} from "../../../../contracts/adapters/Wormhole/IWormholeRelayer.sol"; 5 | import {IWormholeReceiver} from "../../../../contracts/adapters/Wormhole/IWormholeReceiver.sol"; 6 | 7 | contract WormholeSample is IWormholeReceiver { 8 | uint256 public value; 9 | 10 | IWormholeRelayer public immutable WORMHOLE_RELAYER; 11 | uint16 internal immutable WORMHOLE_CHAIN_ID; 12 | uint256 internal constant GAS_LIMIT = 900000; 13 | uint256 internal constant RECEIVER_VALUE = 0; 14 | 15 | constructor(address _wormholeRelayer, uint16 wormholeChainId) { 16 | WORMHOLE_RELAYER = IWormholeRelayer(_wormholeRelayer); 17 | WORMHOLE_CHAIN_ID = wormholeChainId; 18 | } 19 | 20 | function setRemoteValue( 21 | uint16 destinationChainId, 22 | address destinationAddress, 23 | bytes memory payload 24 | ) external payable { 25 | (uint256 nativePriceQuote, ) = WORMHOLE_RELAYER.quoteEVMDeliveryPrice( 26 | destinationChainId, 27 | RECEIVER_VALUE, 28 | GAS_LIMIT 29 | ); 30 | WORMHOLE_RELAYER.sendPayloadToEvm{value: nativePriceQuote}( 31 | destinationChainId, 32 | destinationAddress, 33 | payload, 34 | RECEIVER_VALUE, 35 | GAS_LIMIT, 36 | WORMHOLE_CHAIN_ID, 37 | msg.sender 38 | ); 39 | } 40 | 41 | function receiveWormholeMessages( 42 | bytes memory payload, 43 | bytes[] memory, 44 | bytes32, // sourceAddress, 45 | uint16, // sourceChain, 46 | bytes32 // deliveryHash 47 | ) public payable { 48 | if (payload.length > 0) (value) += abi.decode(payload, (uint256)); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /test/contracts/samples/control/WormholeTextSample.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity 0.8.18; 4 | import {IWormholeRelayer} from "../../../../contracts/adapters/Wormhole/IWormholeRelayer.sol"; 5 | import {IWormholeReceiver} from "../../../../contracts/adapters/Wormhole/IWormholeReceiver.sol"; 6 | 7 | contract WormholeTextSample is IWormholeReceiver { 8 | string public value; 9 | 10 | IWormholeRelayer public immutable WORMHOLE_RELAYER; 11 | uint16 internal immutable WORMHOLE_CHAIN_ID; 12 | uint256 internal constant GAS_LIMIT = 900000; 13 | uint256 internal constant RECEIVER_VALUE = 0; 14 | 15 | constructor(address _wormholeRelayer, uint16 wormholeChainId) { 16 | WORMHOLE_RELAYER = IWormholeRelayer(_wormholeRelayer); 17 | WORMHOLE_CHAIN_ID = wormholeChainId; 18 | } 19 | 20 | function setRemoteValue( 21 | uint16 destinationChainId, 22 | address destinationAddress, 23 | bytes memory payload 24 | ) external payable { 25 | (uint256 nativePriceQuote, ) = WORMHOLE_RELAYER.quoteEVMDeliveryPrice( 26 | destinationChainId, 27 | RECEIVER_VALUE, 28 | GAS_LIMIT 29 | ); 30 | WORMHOLE_RELAYER.sendPayloadToEvm{value: nativePriceQuote}( 31 | destinationChainId, 32 | destinationAddress, 33 | payload, 34 | RECEIVER_VALUE, 35 | GAS_LIMIT, 36 | WORMHOLE_CHAIN_ID, 37 | msg.sender 38 | ); 39 | } 40 | 41 | function receiveWormholeMessages( 42 | bytes memory payload, 43 | bytes[] memory, 44 | bytes32, // sourceAddress, 45 | uint16, // sourceChain, 46 | bytes32 // deliveryHash 47 | ) public payable { 48 | value = abi.decode(payload, (string)); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /test/contracts/samples/token/BasicXERC20Sample.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | import {XERC20Basic} from "../../../../contracts/token/XERC20.sol"; 4 | 5 | contract BasicXERC20Sample is XERC20Basic { 6 | constructor(address owner_) XERC20Basic("Basic XERC20 Sample Token", "BASIC_XERC20", owner_) { 7 | _mint(owner_, 10 ** 18); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/contracts/samples/token/ERC20Sample.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 4 | import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; 5 | 6 | contract ERC20Sample is ERC20, Ownable { 7 | constructor(address owner_) ERC20("Legacy Sample Token", "LEGACY_SAMPLE") { 8 | transferOwnership(owner_); 9 | _mint(owner_, 10e18); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/contracts/samples/token/XERC20LockboxSample.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | import {XERC20Lockbox} from "../../../../contracts/token/XERC20Lockbox.sol"; 4 | 5 | contract XERC20LockboxSample is XERC20Lockbox { 6 | constructor( 7 | address _xerc20, 8 | address _erc20, 9 | bool _isNative, 10 | address owner_ 11 | ) XERC20Lockbox(_xerc20, _erc20, false) {} 12 | } 13 | -------------------------------------------------------------------------------- /test/contracts/samples/token/XERC20NativeLockboxSample.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | import {XERC20Lockbox} from "../../../../contracts/token/XERC20Lockbox.sol"; 4 | 5 | contract XERC20NativeLockboxSample is XERC20Lockbox { 6 | constructor( 7 | address _xerc20, 8 | address _erc20, 9 | bool _isNative, 10 | address owner_ 11 | ) XERC20Lockbox(_xerc20, _erc20, true) {} 12 | } 13 | -------------------------------------------------------------------------------- /test/contracts/samples/token/XERC20Sample.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | import {XERC20} from "../../../../contracts/token/XERC20.sol"; 4 | 5 | contract XERC20Sample is XERC20 { 6 | constructor(address owner_) XERC20("Glacis xERC20 Sample Token", "GLACIS xERC20 SAMPLE", owner_) { 7 | _mint(owner_, 10 ** 18); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/lib/address-string.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity 0.8.18; 4 | import {AddressString} from "../../contracts/libraries/AddressString.sol"; 5 | import {CheckSum} from "../contracts/libraries/CheckSum.sol"; 6 | import {LocalTestSetup} from "../LocalTestSetup.sol"; 7 | /* solhint-disable no-console */ 8 | // solhint-disable-next-line no-global-import 9 | import "forge-std/console.sol"; 10 | 11 | /* solhint-disable contract-name-camelcase */ 12 | contract AddressStringTests is LocalTestSetup { 13 | using AddressString for string; 14 | using CheckSum for address; 15 | 16 | function test__toString() external { 17 | address addr = address(0x1234567890AbcdEF1234567890aBcdef12345678); 18 | string memory addrStr = addr.toChecksumString(); 19 | assertEq(addrStr, "0x1234567890AbcdEF1234567890aBcdef12345678"); 20 | } 21 | 22 | function test__toAddress() external { 23 | string memory addrStr = "1234567890AbcdEF1234567890aBcdef12345678"; 24 | address addr = addrStr.toAddress(); 25 | assertEq(addr, address(0x1234567890AbcdEF1234567890aBcdef12345678)); 26 | } 27 | 28 | function testFuzz__toStringAndBack(address addr) external { 29 | string memory addrStr = addr.getChecksum(); 30 | address newAddress = addrStr.toAddress(); 31 | assertEq(addr, newAddress); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /test/router/router.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity 0.8.18; 4 | import {LocalTestSetup, GlacisRouter, GlacisCommons} from "../LocalTestSetup.sol"; 5 | import {GlacisAbstractRouter__InvalidAdapterAddress, GlacisAbstractRouter__GMPIDCannotBeZero, GlacisAbstractRouter__GMPIDTooHigh} from "../../contracts/routers/GlacisAbstractRouter.sol"; 6 | import "forge-std/console.sol"; 7 | 8 | contract RouterTests is LocalTestSetup { 9 | GlacisRouter internal glacisRouter; 10 | 11 | function setUp() public { 12 | glacisRouter = deployGlacisRouter(); 13 | } 14 | 15 | function test__GlacisRouter_RegistersAdapter(address adapter, uint8 gmpID) external { 16 | vm.assume(adapter != address(0)); 17 | vm.assume(gmpID <= GLACIS_RESERVED_IDS); 18 | vm.assume(gmpID > 0); 19 | 20 | glacisRouter.registerAdapter(gmpID, adapter); 21 | 22 | assertEq(glacisRouter.glacisGMPIdToAdapter(gmpID), adapter); 23 | assertEq(glacisRouter.adapterToGlacisGMPId(adapter), gmpID); 24 | } 25 | 26 | function test__GlacisRouter_ReplacesAdapter(address adapter, address replacingAdapter, uint8 gmpID) external { 27 | vm.assume(adapter != address(0)); 28 | vm.assume(replacingAdapter != address(0)); 29 | vm.assume(gmpID <= GLACIS_RESERVED_IDS); 30 | vm.assume(gmpID > 0); 31 | 32 | glacisRouter.registerAdapter(gmpID, adapter); 33 | 34 | assertEq(glacisRouter.glacisGMPIdToAdapter(gmpID), adapter); 35 | assertEq(glacisRouter.adapterToGlacisGMPId(adapter), gmpID); 36 | 37 | glacisRouter.registerAdapter(gmpID, replacingAdapter); 38 | 39 | assertEq(glacisRouter.glacisGMPIdToAdapter(gmpID), replacingAdapter); 40 | assertEq(glacisRouter.adapterToGlacisGMPId(replacingAdapter), gmpID); 41 | } 42 | 43 | function test__GlacisRouter_DeletesAdapter(address adapter, uint8 gmpID) external { 44 | vm.assume(adapter != address(0)); 45 | vm.assume(gmpID <= GLACIS_RESERVED_IDS); 46 | vm.assume(gmpID > 0); 47 | 48 | glacisRouter.registerAdapter(gmpID, adapter); 49 | glacisRouter.unRegisterAdapter(gmpID); 50 | 51 | assertEq(glacisRouter.glacisGMPIdToAdapter(gmpID), address(0)); 52 | assertEq(glacisRouter.adapterToGlacisGMPId(adapter), 0); 53 | } 54 | 55 | function test__GlacisRouter_DoesNotAllowBadAdapters() external { 56 | vm.expectRevert(GlacisAbstractRouter__InvalidAdapterAddress.selector); 57 | glacisRouter.registerAdapter(1, address(0)); 58 | } 59 | 60 | function test__GlacisRouter_AddAdapterDoesNotAllowBadIDs() external { 61 | vm.expectRevert(GlacisAbstractRouter__GMPIDCannotBeZero.selector); 62 | glacisRouter.registerAdapter(0, address(0x123)); 63 | 64 | vm.expectRevert(GlacisAbstractRouter__GMPIDTooHigh.selector); 65 | glacisRouter.registerAdapter(uint8(GLACIS_RESERVED_IDS) + 1, address(0x123)); 66 | } 67 | 68 | function test__GlacisRouter_NonOwnersCannotAddOrDeleteAdapters() external { 69 | vm.prank(address(0x123)); 70 | vm.expectRevert("Ownable: caller is not the owner"); 71 | glacisRouter.registerAdapter(1, address(0x123)); 72 | } 73 | 74 | receive() external payable {} 75 | } 76 | -------------------------------------------------------------------------------- /test/token/mediator.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity 0.8.18; 4 | import {LocalTestSetup, GlacisAxelarAdapter, GlacisRouter, AxelarGatewayMock, AxelarGasServiceMock, LayerZeroV2Mock} from "../LocalTestSetup.sol"; 5 | import {GlacisClientSample} from "../contracts/samples/GlacisClientSample.sol"; 6 | import {GlacisTokenMediator__OnlyTokenMediatorAllowed} from "../../contracts/mediators/GlacisTokenMediator.sol"; 7 | import {AddressBytes32} from "../../contracts/libraries/AddressBytes32.sol"; 8 | 9 | import {GlacisTokenMediator, XERC20Sample} from "../LocalTestSetup.sol"; 10 | 11 | contract TokenMediatorTests is LocalTestSetup { 12 | using AddressBytes32 for address; 13 | 14 | AxelarGatewayMock internal axelarGatewayMock; 15 | AxelarGasServiceMock internal axelarGasServiceMock; 16 | GlacisAxelarAdapter internal axelarAdapter; 17 | GlacisRouter internal glacisRouter; 18 | GlacisClientSample internal clientSample; 19 | GlacisTokenMediator internal glacisTokenMediator; 20 | XERC20Sample internal xERC20Sample; 21 | 22 | function setUp() public { 23 | glacisRouter = deployGlacisRouter(); 24 | ( 25 | glacisTokenMediator, 26 | xERC20Sample, 27 | , 28 | , 29 | , 30 | , 31 | 32 | ) = deployGlacisTokenFixture(glacisRouter); 33 | (axelarGatewayMock, axelarGasServiceMock) = deployAxelarFixture(); 34 | axelarAdapter = deployAxelarAdapters( 35 | glacisRouter, 36 | axelarGatewayMock, 37 | axelarGasServiceMock 38 | ); 39 | LayerZeroV2Mock lzEndpoint = deployLayerZeroFixture(); 40 | deployLayerZeroAdapters(glacisRouter, lzEndpoint); 41 | } 42 | 43 | function addRemoteMediator(uint256 chainId, bytes32 addr) internal { 44 | uint256[] memory chainIdArr = new uint256[](1); 45 | chainIdArr[0] = chainId; 46 | bytes32[] memory addrArr = new bytes32[](1); 47 | addrArr[0] = addr; 48 | glacisTokenMediator.addRemoteCounterparts(chainIdArr, addrArr); 49 | } 50 | 51 | function test__TokenMediator_AddsRemoteAddress( 52 | bytes32 addr, 53 | uint256 chainId 54 | ) external { 55 | vm.assume(chainId != 0); 56 | addRemoteMediator(chainId, addr); 57 | assertEq(glacisTokenMediator.getRemoteCounterpart(chainId), addr); 58 | } 59 | 60 | function test__TokenMediator_RemovesRemoteAddress( 61 | address addr, 62 | uint256 chainId 63 | ) external { 64 | vm.assume(chainId != 0); 65 | addRemoteMediator(chainId, addr.toBytes32()); 66 | glacisTokenMediator.removeRemoteCounterpart(chainId); 67 | assertEq(glacisTokenMediator.getRemoteCounterpart(chainId), bytes32(0)); 68 | } 69 | 70 | function test__TokenMediator_NonOwnersCannotAddRemote() external { 71 | vm.startPrank(address(0x123)); 72 | vm.expectRevert("Ownable: caller is not the owner"); 73 | addRemoteMediator(block.chainid, address(0x123).toBytes32()); 74 | } 75 | 76 | function test__TokenMediator_NonOwnersCannotRemoveRemote() external { 77 | vm.startPrank(address(0x123)); 78 | vm.expectRevert("Ownable: caller is not the owner"); 79 | glacisTokenMediator.removeRemoteCounterpart(block.chainid); 80 | } 81 | 82 | function test__TokenMediator_RejectsExecuteFromNonMediatorSources( 83 | address addr, 84 | address otherAddr, 85 | uint256 chainId 86 | ) external { 87 | vm.assume(addr != otherAddr); 88 | vm.assume(chainId != 0); 89 | 90 | addRemoteMediator(chainId, addr.toBytes32()); 91 | 92 | // Message is being received by the router 93 | vm.startPrank(address(glacisRouter)); 94 | 95 | address[] memory gmpArray = new address[](1); 96 | gmpArray[0] = AXELAR_GMP_ID; 97 | 98 | vm.expectRevert(GlacisTokenMediator__OnlyTokenMediatorAllowed.selector); 99 | glacisTokenMediator.receiveMessage( 100 | gmpArray, 101 | chainId, 102 | address(otherAddr).toBytes32(), // fromAddress; this is what we're testing for 103 | bytes("") 104 | ); 105 | } 106 | 107 | function test__TokenMediator_AcceptsExecuteFromMediatorSource( 108 | address addr, 109 | uint256 chainId 110 | ) external { 111 | vm.assume(chainId != 0); 112 | 113 | addRemoteMediator(chainId, addr.toBytes32()); 114 | 115 | // Message is being received by the router 116 | vm.startPrank(address(glacisRouter)); 117 | 118 | address[] memory gmpArray = new address[](1); 119 | gmpArray[0] = AXELAR_GMP_ID; 120 | 121 | glacisTokenMediator.receiveMessage( 122 | gmpArray, 123 | chainId, 124 | addr.toBytes32(), 125 | abi.encode( 126 | address(0x123), 127 | address(0x123), 128 | address(xERC20Sample), 129 | address(xERC20Sample), 130 | 1, 131 | bytes("") 132 | ) 133 | ); 134 | assertEq(xERC20Sample.balanceOf(address(0x123)), 1); 135 | } 136 | 137 | function test__TokenMediator_IsAllowedRouteFalseWhenSendToEOAWithoutRemoteMediator( 138 | uint256 chainId 139 | ) external { 140 | vm.assume(chainId != 0); 141 | bytes memory payload = abi.encode( 142 | address(0x123), // EOA 143 | msg.sender, 144 | address(xERC20Sample), 145 | address(xERC20Sample), 146 | 1, 147 | bytes("") 148 | ); 149 | bool isAllowed = glacisTokenMediator.isAllowedRoute( 150 | GlacisRoute( 151 | chainId, 152 | address(0x456).toBytes32(), // wrong mediator address (what we're testing) 153 | AXELAR_GMP_ID 154 | ), 155 | payload 156 | ); 157 | assertFalse(isAllowed); 158 | } 159 | 160 | function test__TokenMediator_IsAllowedRouteTrueWhenSendToEOAWithRemoteMediator( 161 | address addr, 162 | uint256 chainId 163 | ) external { 164 | vm.assume(chainId != 0); 165 | vm.assume(addr != address(0) && addr.code.length == 0); 166 | bytes memory payload = abi.encode( 167 | address(0x123), // EOA 168 | msg.sender, 169 | address(xERC20Sample), 170 | address(xERC20Sample), 171 | 1, 172 | bytes("") 173 | ); 174 | addRemoteMediator(chainId, addr.toBytes32()); 175 | bool isAllowed = glacisTokenMediator.isAllowedRoute( 176 | GlacisRoute( 177 | chainId, 178 | addr.toBytes32(), // correct mediator address (what we're testing) 179 | AXELAR_GMP_ID 180 | ), 181 | payload 182 | ); 183 | assertTrue(isAllowed); 184 | } 185 | 186 | function test__TokenMediator_IsAllowedRouteFalseWhenSendToEOAWithCustomAdapter( 187 | address addr, 188 | uint256 chainId 189 | ) external { 190 | vm.assume(chainId != 0); 191 | vm.assume(addr != address(0) && addr.code.length == 0); 192 | bytes memory payload = abi.encode( 193 | address(0x123), // EOA 194 | msg.sender, 195 | address(xERC20Sample), 196 | address(xERC20Sample), 197 | 1, 198 | bytes("") 199 | ); 200 | addRemoteMediator(chainId, addr.toBytes32()); 201 | bool isAllowed = glacisTokenMediator.isAllowedRoute( 202 | GlacisRoute( 203 | chainId, 204 | addr.toBytes32(), // correct mediator address 205 | address(0x123456789) 206 | ), // Custom Adapter 207 | payload 208 | ); 209 | assertFalse(isAllowed); 210 | } 211 | } 212 | --------------------------------------------------------------------------------