├── .solhintignore ├── .gitignore ├── whitepaper └── CCTPV2_White_Paper.pdf ├── pictures ├── Solidity-Change-Compiler.png └── Solidity-Compiler-Version.png ├── foundry.toml ├── remappings.txt ├── .solhint.json ├── package.json ├── mythril.config.json ├── NOTICES ├── Dockerfile ├── SECURITY.md ├── .github └── workflows │ ├── ci-olympix.yml │ └── ci.yml ├── docs ├── abis │ └── cctp │ │ ├── proxy │ │ ├── Initializable.json │ │ └── AdminUpgradableProxy.json │ │ ├── Message.json │ │ ├── v2.1 │ │ ├── AddressUtilsExternal.json │ │ ├── Create2Factory.json │ │ └── Denylistable.json │ │ ├── Ownable.json │ │ ├── v2 │ │ ├── AddressUtilsExternal.json │ │ ├── Create2Factory.json │ │ └── Denylistable.json │ │ ├── Ownable2Step.json │ │ ├── Rescuable.json │ │ ├── Pausable.json │ │ └── TokenController.json ├── package.json ├── README.md └── index.js ├── .vscode └── settings.json ├── .gitmodules ├── .licenseignore ├── test ├── mocks │ ├── v2 │ │ ├── MockPayableProxyImplementation.sol │ │ ├── MockDenylistable.sol │ │ ├── MockTokenMessengerV3.sol │ │ ├── MockHookTarget.sol │ │ ├── MockMessageTransmitterV3.sol │ │ ├── MockProxyImplementation.sol │ │ └── MockReentrantCallerV2.sol │ ├── MockTokenMessenger.sol │ ├── MockInitializableImplementation.sol │ └── MockReentrantCaller.sol ├── scripts │ └── v2 │ │ ├── RotateKeysV2.t.sol │ │ ├── SetupRemoteResourcesV2.t.sol │ │ ├── DeployImplementationsV2.t.sol │ │ └── PredictCreate2Deployments.t.sol ├── messages │ ├── v2 │ │ ├── AddressUtils.t.sol │ │ ├── AddressUtilsExternal.t.sol │ │ ├── MessageV2.t.sol │ │ └── BurnMessageV2.t.sol │ ├── BurnMessage.t.sol │ └── Message.t.sol ├── roles │ └── Ownable2Step.t.sol └── v2 │ ├── Create2Factory.t.sol │ └── Initializable.t.sol ├── requirements.txt ├── src ├── interfaces │ ├── IMessageTransmitter.sol │ ├── v2 │ │ ├── IReceiverV2.sol │ │ ├── IMessageTransmitterV2.sol │ │ ├── ITokenMinterV2.sol │ │ ├── IRelayerV2.sol │ │ └── IMessageHandlerV2.sol │ ├── IReceiver.sol │ ├── IMessageHandler.sol │ ├── IMintBurnToken.sol │ ├── ITokenMinter.sol │ └── IRelayer.sol ├── v2 │ ├── FinalityThresholds.sol │ ├── Create2Factory.sol │ ├── TokenMinterV2.sol │ └── BaseMessageTransmitter.sol ├── roles │ ├── v2 │ │ ├── AttestableV2.sol │ │ └── Denylistable.sol │ ├── Pausable.sol │ ├── Rescuable.sol │ ├── Ownable2Step.sol │ └── Ownable.sol ├── messages │ ├── v2 │ │ ├── AddressUtils.sol │ │ ├── AddressUtilsExternal.sol │ │ └── BurnMessageV2.sol │ └── BurnMessage.sol └── proxy │ └── AdminUpgradableProxy.sol ├── scripts ├── v2 │ ├── Salts.sol │ ├── DeployAddressUtilsExternal.s.sol │ ├── SetupRemoteResourcesV2.s.sol │ ├── PredictCreate2Deployments.s.sol │ ├── RotateKeysV2.s.sol │ └── DeployImplementationsV2.s.sol ├── DeployCreate2Factory.s.sol └── precomputeRemoteMessengerAddress.py └── Makefile /.solhintignore: -------------------------------------------------------------------------------- 1 | lib/ 2 | node_modules/ 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | cache/ 2 | node_modules/ 3 | out/ 4 | *.DS_Store 5 | __pycache__/ 6 | venv/ 7 | broadcast/ 8 | .env 9 | -------------------------------------------------------------------------------- /whitepaper/CCTPV2_White_Paper.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/circlefin/evm-cctp-contracts/HEAD/whitepaper/CCTPV2_White_Paper.pdf -------------------------------------------------------------------------------- /pictures/Solidity-Change-Compiler.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/circlefin/evm-cctp-contracts/HEAD/pictures/Solidity-Change-Compiler.png -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = 'src' 3 | out = 'out' 4 | libs = ['lib'] 5 | optimizer_runs = 100000 6 | [fuzz] 7 | runs = 10000 8 | -------------------------------------------------------------------------------- /pictures/Solidity-Compiler-Version.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/circlefin/evm-cctp-contracts/HEAD/pictures/Solidity-Compiler-Version.png -------------------------------------------------------------------------------- /remappings.txt: -------------------------------------------------------------------------------- 1 | @memview-sol/=lib/memview-sol 2 | @openzeppelin/=lib/openzeppelin-contracts 3 | ds-test/=lib/ds-test/src/ 4 | forge-std/=lib/forge-std/src/ -------------------------------------------------------------------------------- /.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solhint:recommended", 3 | "rules": { 4 | "compiler-version": ["error", "^0.7.6"], 5 | "avoid-sha3": "warn", 6 | "no-unused-vars": "warn" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "evm-cctp-contracts", 3 | "license": "Apache-2.0", 4 | "scripts": { 5 | "lint": "solhint '**/*.sol'" 6 | }, 7 | "devDependencies": { 8 | "solhint": "^3.3.7" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /mythril.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "remappings": [ 3 | "@memview-sol/=lib/memview-sol/", 4 | "@openzeppelin/=lib/openzeppelin-contracts/", 5 | "ds-test/=lib/ds-test/src/", 6 | "forge-std/=lib/forge-std/src/" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /NOTICES: -------------------------------------------------------------------------------- 1 | This repository includes files inspired by https://github.com/nomad-xyz/monorepo, including but not limited to src/messages/Message.sol, src/messages/BurnMessage.sol, src/interfaces/IMessageHandler.sol, src/TokenMessenger.sol, and src/MessageTransmitter.sol. 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ARG FOUNDRY_VERSION=nightly-3fa02706ca732c994715ba42d923605692062375 2 | # Use fixed foundry image 3 | FROM ghcr.io/foundry-rs/foundry:${FOUNDRY_VERSION} 4 | 5 | # Copy our source code into the container 6 | WORKDIR /app 7 | COPY . . 8 | 9 | # Build the source code 10 | EXPOSE 8545 11 | RUN forge build 12 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting a Vulnerability 4 | Please do not file public issues on Github for security vulnerabilities. All security vulnerabilities should be reported to Circle privately, through Circle's [Bug Bounty Program](https://hackerone.com/circle-bbp). Please read through the program policy before submitting a report. -------------------------------------------------------------------------------- /.github/workflows/ci-olympix.yml: -------------------------------------------------------------------------------- 1 | name: "Olympix Scan" 2 | on: 3 | pull_request: 4 | branches: [ "master" ] 5 | workflow_dispatch: 6 | schedule: 7 | - cron: '31 14 * * 1' # Every Monday 2:31PM UTC 8 | 9 | jobs: 10 | run_olympix: 11 | if: ${{ github.repository_owner == 'circlefin' }} 12 | uses: circlefin/security-seceng-templates/.github/workflows/olympix_scan.yml@v1 13 | -------------------------------------------------------------------------------- /docs/abis/cctp/proxy/Initializable.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "event", 4 | "name": "Initialized", 5 | "inputs": [ 6 | { 7 | "name": "version", 8 | "type": "uint64", 9 | "indexed": false, 10 | "internalType": "uint64" 11 | } 12 | ], 13 | "anonymous": false 14 | } 15 | ] -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docs", 3 | "version": "1.0.0", 4 | "description": "CCTP Quickstart", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node index.js" 8 | }, 9 | "author": "", 10 | "license": "Apache-2.0", 11 | "dependencies": { 12 | "ethers": "^5.7.2", 13 | "web3": "4.16.0" 14 | }, 15 | "devDependencies": { 16 | "dotenv": "^16.0.3" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "solidity.compilerOptimization": 200, 4 | "solidity.enabledAsYouTypeCompilationErrorCheck": true, 5 | "solidity.compileUsingRemoteVersion": "v0.7.6+commit.7338295f", 6 | "solidity.formatter": "prettier", 7 | "solidity.linter": "solhint", 8 | "solidity.validationDelay": 1500, 9 | "[solidity]": { 10 | "editor.tabSize": 4, 11 | }, 12 | "security.olympix.project.includePath": "/src", 13 | "security.olympix.project.testsPath": "/test" 14 | } 15 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/forge-std"] 2 | path = lib/forge-std 3 | url = https://github.com/foundry-rs/forge-std 4 | [submodule "lib/memview-sol"] 5 | path = lib/memview-sol 6 | url = https://github.com/summa-tx/memview-sol 7 | [submodule "lib/openzeppelin-contracts"] 8 | path = lib/openzeppelin-contracts 9 | url = https://github.com/OpenZeppelin/openzeppelin-contracts 10 | [submodule "lib/ds-test"] 11 | path = lib/ds-test 12 | url = https://github.com/dapphub/ds-test 13 | [submodule "lib/centre-tokens.git"] 14 | path = lib/centre-tokens.git 15 | url = https://github.com/centrehq/centre-tokens.git 16 | -------------------------------------------------------------------------------- /.licenseignore: -------------------------------------------------------------------------------- 1 | pkg:npm/web3@4.16.0 2 | pkg:npm/@ethereumjs/rlp@5.0.2 3 | pkg:pypi/cytoolz@0.12.3 4 | pkg:pypi/protobuf@6.32.0 5 | pkg:npm/web3-core@4.7.1 6 | pkg:npm/web3-errors@1.3.1 7 | pkg:npm/web3-eth@4.11.1 8 | pkg:npm/web3-eth-abi@4.1 9 | pkg:npm/web3-eth-contract@4.7.2 10 | pkg:npm/web3-eth-ens@4.0 11 | pkg:npm/web3-eth-iban@4.0.7 12 | pkg:npm/web3-eth-personal@4.1.0 13 | pkg:npm/web3-net@4.1.0 14 | pkg:npm/web3-providers-http@4.2.0 15 | pkg:npm/web3-providers-ipc@4.0.7 16 | pkg:npm/web3-providers-ws@4.0.8 17 | pkg:npm/web3-rpc-methods@1.3.0 18 | pkg:npm/web3-rpc-providers@1.0.0-rc.4 19 | pkg:npm/web3-types@1.10.0 20 | pkg:npm/web3-utils@4.3.3 21 | pkg:npm/web3-validator@2.0.6 22 | pkg:npm/web3-eth-accounts@4.3.1 23 | -------------------------------------------------------------------------------- /docs/abis/cctp/Message.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "inputs": [ 4 | { 5 | "internalType": "address", 6 | "name": "addr", 7 | "type": "address" 8 | } 9 | ], 10 | "name": "addressToBytes32", 11 | "outputs": [ 12 | { 13 | "internalType": "bytes32", 14 | "name": "", 15 | "type": "bytes32" 16 | } 17 | ], 18 | "stateMutability": "pure", 19 | "type": "function" 20 | }, 21 | { 22 | "inputs": [ 23 | { 24 | "internalType": "bytes32", 25 | "name": "_buf", 26 | "type": "bytes32" 27 | } 28 | ], 29 | "name": "bytes32ToAddress", 30 | "outputs": [ 31 | { 32 | "internalType": "address", 33 | "name": "", 34 | "type": "address" 35 | } 36 | ], 37 | "stateMutability": "pure", 38 | "type": "function" 39 | } 40 | ] -------------------------------------------------------------------------------- /docs/abis/cctp/v2.1/AddressUtilsExternal.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "function", 4 | "name": "addressToBytes32", 5 | "inputs": [ 6 | { 7 | "name": "addr", 8 | "type": "address", 9 | "internalType": "address" 10 | } 11 | ], 12 | "outputs": [ 13 | { 14 | "name": "", 15 | "type": "bytes32", 16 | "internalType": "bytes32" 17 | } 18 | ], 19 | "stateMutability": "pure" 20 | }, 21 | { 22 | "type": "function", 23 | "name": "bytes32ToAddress", 24 | "inputs": [ 25 | { 26 | "name": "_buf", 27 | "type": "bytes32", 28 | "internalType": "bytes32" 29 | } 30 | ], 31 | "outputs": [ 32 | { 33 | "name": "", 34 | "type": "address", 35 | "internalType": "address" 36 | } 37 | ], 38 | "stateMutability": "pure" 39 | } 40 | ] -------------------------------------------------------------------------------- /test/mocks/v2/MockPayableProxyImplementation.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Circle Internet Group, Inc. All rights reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | pragma solidity 0.7.6; 19 | 20 | contract MockPayableProxyImplementation { 21 | receive() external payable {} 22 | } 23 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aiohttp==3.8.1 2 | aiosignal==1.2.0 3 | async-timeout==4.0.2 4 | attrs==22.1.0 5 | base58==2.1.1 6 | bitarray==2.6.0 7 | certifi==2022.12.7 8 | charset-normalizer==2.1.0 9 | cytoolz==0.12.3 10 | eth-abi==5.2.0 11 | eth-account==0.11.3 12 | eth-hash==0.7.1 13 | eth-keyfile==0.9.1 14 | eth-keys==0.7.0 15 | eth-rlp==1.0.1 16 | eth-typing==4.4.0 17 | eth-utils==4.1.1 18 | frozenlist==1.3.1 19 | hexbytes==0.3.1 20 | idna==3.3 21 | ipfshttpclient==0.8.0a2 22 | jsonschema==4.9.1 23 | lru-dict==1.2.0 24 | multiaddr==0.0.9 25 | multidict==6.0.2 26 | netaddr==0.8.0 27 | parsimonious==0.10.0 28 | protobuf==6.32.0 29 | py-solc-x==1.1.1 30 | pycryptodome==3.15.0 31 | pyrsistent==0.18.1 32 | python-dotenv==0.21.0 33 | requests==2.28.1 34 | rlp==4.1.0 35 | semantic-version==2.10.0 36 | six==1.16.0 37 | toolz==0.12.0 38 | urllib3==1.26.11 39 | varint==1.0.2 40 | web3==6.20.3 41 | websockets==15.0.1 42 | yarl==1.8.1 43 | -------------------------------------------------------------------------------- /src/interfaces/IMessageTransmitter.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, Circle Internet Financial Limited. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | pragma solidity 0.7.6; 17 | 18 | import "./IRelayer.sol"; 19 | import "./IReceiver.sol"; 20 | 21 | /** 22 | * @title IMessageTransmitter 23 | * @notice Interface for message transmitters, which both relay and receive messages. 24 | */ 25 | interface IMessageTransmitter is IRelayer, IReceiver { 26 | 27 | } 28 | -------------------------------------------------------------------------------- /docs/abis/cctp/Ownable.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "anonymous": false, 4 | "inputs": [ 5 | { 6 | "indexed": true, 7 | "internalType": "address", 8 | "name": "previousOwner", 9 | "type": "address" 10 | }, 11 | { 12 | "indexed": true, 13 | "internalType": "address", 14 | "name": "newOwner", 15 | "type": "address" 16 | } 17 | ], 18 | "name": "OwnershipTransferred", 19 | "type": "event" 20 | }, 21 | { 22 | "inputs": [], 23 | "name": "owner", 24 | "outputs": [ 25 | { 26 | "internalType": "address", 27 | "name": "", 28 | "type": "address" 29 | } 30 | ], 31 | "stateMutability": "view", 32 | "type": "function" 33 | }, 34 | { 35 | "inputs": [ 36 | { 37 | "internalType": "address", 38 | "name": "newOwner", 39 | "type": "address" 40 | } 41 | ], 42 | "name": "transferOwnership", 43 | "outputs": [], 44 | "stateMutability": "nonpayable", 45 | "type": "function" 46 | } 47 | ] -------------------------------------------------------------------------------- /src/interfaces/v2/IReceiverV2.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Circle Internet Group, Inc. All rights reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | pragma solidity 0.7.6; 19 | 20 | import {IReceiver} from "../IReceiver.sol"; 21 | 22 | /** 23 | * @title IReceiverV2 24 | * @notice Receives messages on the destination chain and forwards them to contracts implementing 25 | * IMessageHandlerV2. 26 | */ 27 | interface IReceiverV2 is IReceiver { 28 | 29 | } 30 | -------------------------------------------------------------------------------- /docs/abis/cctp/v2/AddressUtilsExternal.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "function", 4 | "name": "addressToBytes32", 5 | "inputs": [ 6 | { 7 | "name": "addr", 8 | "type": "address", 9 | "internalType": "address" 10 | } 11 | ], 12 | "outputs": [ 13 | { 14 | "name": "", 15 | "type": "bytes32", 16 | "internalType": "bytes32" 17 | } 18 | ], 19 | "stateMutability": "pure" 20 | }, 21 | { 22 | "type": "function", 23 | "name": "bytes32ToAddress", 24 | "inputs": [ 25 | { 26 | "name": "_buf", 27 | "type": "bytes32", 28 | "internalType": "bytes32" 29 | } 30 | ], 31 | "outputs": [ 32 | { 33 | "name": "", 34 | "type": "address", 35 | "internalType": "address" 36 | } 37 | ], 38 | "stateMutability": "pure" 39 | } 40 | ] -------------------------------------------------------------------------------- /test/mocks/v2/MockDenylistable.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Circle Internet Group, Inc. All rights reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | pragma solidity 0.7.6; 19 | 20 | import {Denylistable} from "../../../src/roles/v2/Denylistable.sol"; 21 | 22 | contract MockDenylistable is Denylistable { 23 | function sensitiveFunction() 24 | external 25 | view 26 | notDenylistedCallers 27 | returns (bool) 28 | { 29 | return true; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/interfaces/v2/IMessageTransmitterV2.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Circle Internet Group, Inc. All rights reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | pragma solidity 0.7.6; 19 | 20 | import {IReceiverV2} from "./IReceiverV2.sol"; 21 | import {IRelayerV2} from "./IRelayerV2.sol"; 22 | 23 | /** 24 | * @title IMessageTransmitterV2 25 | * @notice Interface for V2 message transmitters, which both relay and receive messages. 26 | */ 27 | interface IMessageTransmitterV2 is IRelayerV2, IReceiverV2 { 28 | 29 | } 30 | -------------------------------------------------------------------------------- /scripts/v2/Salts.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Circle Internet Group, Inc. All rights reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | pragma solidity 0.7.6; 19 | 20 | // Salts used for CREATE2 deployments 21 | 22 | bytes32 constant SALT_TOKEN_MINTER = keccak256("cctp.v2.tokenminter"); 23 | bytes32 constant SALT_TOKEN_MESSENGER = keccak256("cctp.v2.tokenmessenger"); 24 | bytes32 constant SALT_MESSAGE_TRANSMITTER = keccak256( 25 | "cctp.v2.messagetransmitter" 26 | ); 27 | bytes32 constant SALT_ADDRESS_UTILS_EXTERNAL = keccak256( 28 | "cctp.v2.addressutilsexternal" 29 | ); 30 | -------------------------------------------------------------------------------- /src/v2/FinalityThresholds.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Circle Internet Group, Inc. All rights reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | pragma solidity 0.7.6; 19 | 20 | // The threshold at which (and above) messages are considered finalized. 21 | uint32 constant FINALITY_THRESHOLD_FINALIZED = 2000; 22 | 23 | // The threshold at which (and above) messages are considered confirmed. 24 | uint32 constant FINALITY_THRESHOLD_CONFIRMED = 1000; 25 | 26 | // The minimum allowed level of finality accepted by TokenMessenger 27 | uint32 constant TOKEN_MESSENGER_MIN_FINALITY_THRESHOLD = 500; 28 | -------------------------------------------------------------------------------- /test/mocks/v2/MockTokenMessengerV3.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Circle Internet Group, Inc. All rights reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | pragma solidity 0.7.6; 19 | pragma abicoder v2; 20 | 21 | import {TokenMessengerV2} from "../../../src/v2/TokenMessengerV2.sol"; 22 | 23 | contract MockTokenMessengerV3 is TokenMessengerV2 { 24 | constructor( 25 | address _messageTransmitter, 26 | uint32 _messageBodyVersion 27 | ) TokenMessengerV2(_messageTransmitter, _messageBodyVersion) {} 28 | 29 | function v3Function() external pure returns (bool) { 30 | return true; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test/mocks/v2/MockHookTarget.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Circle Internet Group, Inc. All rights reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | pragma solidity 0.7.6; 19 | 20 | contract MockHookTarget { 21 | event HookReceived(uint256 paramOne, uint256 paramTwo); 22 | 23 | // Returns the sum of paramOne and paramTwo 24 | function succeedingHook( 25 | uint256 paramOne, 26 | uint256 paramTwo 27 | ) external returns (uint256) { 28 | emit HookReceived(paramOne, paramTwo); 29 | return paramOne + paramTwo; 30 | } 31 | 32 | function failingHook() external pure { 33 | revert("Hook failure"); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/interfaces/IReceiver.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, Circle Internet Financial Limited. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | pragma solidity 0.7.6; 17 | 18 | /** 19 | * @title IReceiver 20 | * @notice Receives messages on destination chain and forwards them to IMessageDestinationHandler 21 | */ 22 | interface IReceiver { 23 | /** 24 | * @notice Receives an incoming message, validating the header and passing 25 | * the body to application-specific handler. 26 | * @param message The message raw bytes 27 | * @param signature The message signature 28 | * @return success bool, true if successful 29 | */ 30 | function receiveMessage(bytes calldata message, bytes calldata signature) 31 | external 32 | returns (bool success); 33 | } 34 | -------------------------------------------------------------------------------- /test/mocks/v2/MockMessageTransmitterV3.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Circle Internet Group, Inc. All rights reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | pragma solidity 0.7.6; 19 | 20 | import {MessageTransmitterV2} from "../../../src/v2/MessageTransmitterV2.sol"; 21 | 22 | contract MockMessageTransmitterV3 is MessageTransmitterV2 { 23 | address public newV3State; 24 | 25 | constructor( 26 | uint32 _localDomain, 27 | uint32 _version 28 | ) MessageTransmitterV2(_localDomain, _version) {} 29 | 30 | function initializeV3(address newState) external reinitializer(2) { 31 | newV3State = newState; 32 | } 33 | 34 | function v3Function() external pure returns (bool) { 35 | return true; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/interfaces/IMessageHandler.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, Circle Internet Financial Limited. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | pragma solidity 0.7.6; 17 | 18 | /** 19 | * @title IMessageHandler 20 | * @notice Handles messages on destination domain forwarded from 21 | * an IReceiver 22 | */ 23 | interface IMessageHandler { 24 | /** 25 | * @notice handles an incoming message from a Receiver 26 | * @param sourceDomain the source domain of the message 27 | * @param sender the sender of the message 28 | * @param messageBody The message raw bytes 29 | * @return success bool, true if successful 30 | */ 31 | function handleReceiveMessage( 32 | uint32 sourceDomain, 33 | bytes32 sender, 34 | bytes calldata messageBody 35 | ) external returns (bool); 36 | } 37 | -------------------------------------------------------------------------------- /test/mocks/MockTokenMessenger.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, Circle Internet Financial Limited. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | pragma solidity 0.7.6; 17 | 18 | import "../../src/interfaces/IMessageHandler.sol"; 19 | 20 | contract MockTokenMessenger is IMessageHandler { 21 | // ============ Constructor ============ 22 | constructor() {} 23 | 24 | function handleReceiveMessage( 25 | uint32 _sourceDomain, 26 | bytes32 _sender, 27 | bytes memory _messageBody 28 | ) external override returns (bool) { 29 | // revert if _messageBody is 'revert', otherwise do nothing 30 | require( 31 | keccak256(_messageBody) != keccak256(bytes("revert")), 32 | "mock revert" 33 | ); 34 | 35 | if (keccak256(_messageBody) == keccak256(bytes("return false"))) { 36 | return false; 37 | } 38 | 39 | return true; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /scripts/DeployCreate2Factory.s.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024, Circle Internet Financial Limited. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | pragma solidity 0.7.6; 17 | 18 | import "forge-std/Script.sol"; 19 | import {Create2Factory} from "../src/v2/Create2Factory.sol"; 20 | 21 | contract DeployCreate2FactoryScript is Script { 22 | address private create2FactoryOwner; 23 | 24 | Create2Factory private create2Factory; 25 | 26 | function deployCreate2Factory( 27 | address _create2FactoryOwner 28 | ) internal returns (Create2Factory _create2Factory) { 29 | vm.startBroadcast(_create2FactoryOwner); 30 | 31 | _create2Factory = new Create2Factory(); 32 | 33 | vm.stopBroadcast(); 34 | } 35 | 36 | function setUp() public { 37 | create2FactoryOwner = vm.envAddress("CREATE2_FACTORY_OWNER"); 38 | } 39 | 40 | function run() public { 41 | create2Factory = deployCreate2Factory(create2FactoryOwner); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /test/scripts/v2/RotateKeysV2.t.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024, Circle Internet Financial Limited. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | pragma solidity 0.7.6; 17 | pragma abicoder v2; 18 | 19 | import {ScriptV2TestUtils} from "./ScriptV2TestUtils.sol"; 20 | 21 | contract RotateKeysTest is ScriptV2TestUtils { 22 | function setUp() public { 23 | _deployCreate2Factory(); 24 | _deployImplementations(); 25 | _deployProxies(); 26 | _setupRemoteResources(); 27 | _rotateKeys(); 28 | } 29 | 30 | function testRotateMessageTransmitterV2Owner() public { 31 | assertEq(messageTransmitterV2.pendingOwner(), newOwner); 32 | } 33 | 34 | function testRotateTokenMessengerV2Owner() public { 35 | assertEq(tokenMessengerV2.pendingOwner(), newOwner); 36 | } 37 | 38 | function testRotateTokenControllerThenTokenMinterV2Owner() public { 39 | assertEq(tokenMinterV2.tokenController(), newOwner); 40 | assertEq(tokenMinterV2.pendingOwner(), newOwner); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /test/messages/v2/AddressUtils.t.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Circle Internet Group, Inc. All rights reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | pragma solidity 0.7.6; 19 | pragma abicoder v2; 20 | 21 | import {Test} from "forge-std/Test.sol"; 22 | import {AddressUtils} from "../../../src/messages/v2/AddressUtils.sol"; 23 | 24 | contract AddressUtilsTest is Test { 25 | function testAddressToBytes32Conversion(address _addr) public pure { 26 | bytes32 _addrAsBytes32 = AddressUtils.toBytes32(_addr); 27 | address _recoveredAddr = AddressUtils.toAddress(_addrAsBytes32); 28 | assertEq(_recoveredAddr, _addr); 29 | } 30 | 31 | function testAddressToBytes32LeftPads(address _addr) public pure { 32 | bytes32 _addrAsBytes32 = AddressUtils.toBytes32(_addr); 33 | 34 | // addresses are 20 bytes, so the first 12 bytes should be 0 (left-padded) 35 | for (uint8 i; i < 12; i++) { 36 | assertEq(_addrAsBytes32[i], 0); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /test/mocks/v2/MockProxyImplementation.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Circle Internet Group, Inc. All rights reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | pragma solidity 0.7.6; 19 | 20 | // Test helper to use an alternate implementation to test proxy 21 | contract MockProxyImplementation { 22 | address public storedAddr; 23 | 24 | function foo() external pure returns (bytes memory response) { 25 | response = bytes("bar"); 26 | } 27 | 28 | function setStoredAddr(address _storedAddr) external { 29 | storedAddr = _storedAddr; 30 | } 31 | } 32 | 33 | // Alternate implementation with distinct ABI 34 | contract MockAlternateProxyImplementation { 35 | uint256[1] __gap; 36 | address public storedAddrAlternate; 37 | 38 | function baz() external pure returns (bytes memory response) { 39 | response = bytes("qux"); 40 | } 41 | 42 | function setStoredAddrAlternate(address _storedAddr) external { 43 | storedAddrAlternate = _storedAddr; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/roles/v2/AttestableV2.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Circle Internet Group, Inc. All rights reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | pragma solidity 0.7.6; 19 | 20 | import {Attestable} from "../Attestable.sol"; 21 | 22 | /** 23 | * @title AttestableV2 24 | * @notice Builds on Attestable by adding a storage gap to enable more flexible future additions to 25 | * any AttestableV2 child contracts. 26 | */ 27 | contract AttestableV2 is Attestable { 28 | /** 29 | * @dev This empty reserved space is put in place to allow future versions to add new 30 | * variables without shifting down storage in the inheritance chain. 31 | * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps 32 | */ 33 | uint256[20] private __gap; 34 | 35 | // ============ Constructor ============ 36 | /** 37 | * @dev The constructor sets the original attester manager and the first enabled attester to the 38 | * msg.sender address. 39 | */ 40 | constructor() Attestable(msg.sender) {} 41 | } 42 | -------------------------------------------------------------------------------- /scripts/precomputeRemoteMessengerAddress.py: -------------------------------------------------------------------------------- 1 | from dotenv import load_dotenv 2 | from web3 import Web3 3 | 4 | import argparse 5 | import os 6 | import rlp 7 | 8 | def compute_address(sender, nonce): 9 | """ 10 | Computes the address for a deployed contract given the address 11 | of the sender and the current nonce. 12 | """ 13 | sender_as_bytes = Web3.toBytes(hexstr=sender) 14 | contract_address = Web3.toHex(Web3.keccak(rlp.encode([sender_as_bytes, nonce])))[-40:] 15 | return contract_address 16 | 17 | def precompute_remote_token_messenger_address(remote_rpc_url): 18 | """ 19 | Computes expected address for remote token messenger contract on the 20 | input remote_rpc_url and writes to the .env file. Requires 21 | REMOTE_TOKEN_MESSENGER_DEPLOYER to be defined in the .env file. 22 | """ 23 | remote_token_messenger_deployer = os.getenv(f"REMOTE_TOKEN_MESSENGER_DEPLOYER") 24 | remote_domain_node = Web3(Web3.HTTPProvider(remote_rpc_url)) 25 | 26 | remote_token_messenger_deployer_nonce = remote_domain_node.eth.get_transaction_count(remote_token_messenger_deployer) 27 | remote_token_messenger_address = compute_address(remote_token_messenger_deployer, remote_token_messenger_deployer_nonce) 28 | 29 | with open(".env", "a") as env_file: 30 | env_file.write(f"REMOTE_TOKEN_MESSENGER_ADDRESS=0x{remote_token_messenger_address}\n") 31 | 32 | load_dotenv() 33 | parser = argparse.ArgumentParser() 34 | parser.add_argument("--REMOTE_RPC_URL", required=True, help="RPC URL for the remote chain") 35 | args = parser.parse_args() 36 | remote_rpc_url = args.REMOTE_RPC_URL 37 | precompute_remote_token_messenger_address(remote_rpc_url) 38 | -------------------------------------------------------------------------------- /test/scripts/v2/SetupRemoteResourcesV2.t.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024, Circle Internet Financial Limited. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | pragma solidity 0.7.6; 17 | pragma abicoder v2; 18 | 19 | import {ScriptV2TestUtils} from "./ScriptV2TestUtils.sol"; 20 | 21 | contract SetupRemoteResourcesTest is ScriptV2TestUtils { 22 | function setUp() public { 23 | _deployCreate2Factory(); 24 | _deployImplementations(); 25 | _deployProxies(); 26 | _setupRemoteResources(); 27 | } 28 | 29 | function testLinkTokenPair() public { 30 | bytes32 remoteKey = keccak256( 31 | abi.encodePacked( 32 | anotherRemoteDomain, 33 | bytes32(uint256(uint160(anotherRemoteToken))) 34 | ) 35 | ); 36 | assertEq(tokenMinterV2.remoteTokensToLocalTokens(remoteKey), token); 37 | } 38 | 39 | function testAddRemoteTokenMessenger() public { 40 | assertEq( 41 | tokenMessengerV2.remoteTokenMessengers(anotherRemoteDomain), 42 | bytes32(uint256(uint160(address(tokenMessengerV2)))) 43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /test/mocks/MockInitializableImplementation.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Circle Internet Group, Inc. All rights reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | pragma solidity 0.7.6; 19 | 20 | import {Initializable} from "../../src/proxy/Initializable.sol"; 21 | 22 | contract MockInitializableImplementation is Initializable { 23 | address public addr; 24 | uint256 public num; 25 | 26 | function initialize(address _addr, uint256 _num) external initializer { 27 | addr = _addr; 28 | num = _num; 29 | } 30 | 31 | function initializeV2() external reinitializer(2) {} 32 | 33 | function initializeV3() external reinitializer(3) {} 34 | 35 | function supportingInitializer() public view onlyInitializing {} 36 | 37 | function disableInitializers() external { 38 | _disableInitializers(); 39 | } 40 | 41 | function initializedVersion() external view returns (uint64) { 42 | return _getInitializedVersion(); 43 | } 44 | 45 | function initializing() external view returns (bool) { 46 | return _isInitializing(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /test/mocks/v2/MockReentrantCallerV2.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Circle Internet Group, Inc. All rights reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | pragma solidity 0.7.6; 19 | 20 | import {IMessageHandlerV2} from "../../../src/interfaces/v2/IMessageHandlerV2.sol"; 21 | import {MockReentrantCaller} from "../MockReentrantCaller.sol"; 22 | import {Address} from "@openzeppelin/contracts/utils/Address.sol"; 23 | 24 | contract MockReentrantCallerV2 is IMessageHandlerV2, MockReentrantCaller { 25 | function handleReceiveFinalizedMessage( 26 | uint32 sourceDomain, 27 | bytes32 sender, 28 | uint32, 29 | bytes calldata messageBody 30 | ) external override returns (bool) { 31 | return handleReceiveMessage(sourceDomain, sender, messageBody); 32 | } 33 | 34 | function handleReceiveUnfinalizedMessage( 35 | uint32 sourceDomain, 36 | bytes32 sender, 37 | uint32, 38 | bytes calldata messageBody 39 | ) external override returns (bool) { 40 | return handleReceiveMessage(sourceDomain, sender, messageBody); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /test/messages/v2/AddressUtilsExternal.t.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Circle Internet Group, Inc. All rights reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | pragma solidity 0.7.6; 19 | pragma abicoder v2; 20 | 21 | import {Test} from "forge-std/Test.sol"; 22 | import {AddressUtilsExternal} from "../../../src/messages/v2/AddressUtilsExternal.sol"; 23 | 24 | contract AddressUtilsExternalTest is Test { 25 | function testAddressToBytes32Conversion(address _addr) public pure { 26 | bytes32 _addrAsBytes32 = AddressUtilsExternal.addressToBytes32(_addr); 27 | address _recoveredAddr = AddressUtilsExternal.bytes32ToAddress( 28 | _addrAsBytes32 29 | ); 30 | assertEq(_recoveredAddr, _addr); 31 | } 32 | 33 | function testAddressToBytes32LeftPads(address _addr) public pure { 34 | bytes32 _addrAsBytes32 = AddressUtilsExternal.addressToBytes32(_addr); 35 | 36 | // addresses are 20 bytes, so the first 12 bytes should be 0 (left-padded) 37 | for (uint8 i; i < 12; i++) { 38 | assertEq(_addrAsBytes32[i], 0); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/interfaces/IMintBurnToken.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, Circle Internet Financial Limited. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | pragma solidity 0.7.6; 17 | 18 | import "../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; 19 | 20 | /** 21 | * @title IMintBurnToken 22 | * @notice interface for mintable and burnable ERC20 token 23 | */ 24 | interface IMintBurnToken is IERC20 { 25 | /** 26 | * @dev Function to mint tokens 27 | * @param to The address that will receive the minted tokens. 28 | * @param amount The amount of tokens to mint. Must be less than or equal 29 | * to the minterAllowance of the caller. 30 | * @return A boolean that indicates if the operation was successful. 31 | */ 32 | function mint(address to, uint256 amount) external returns (bool); 33 | 34 | /** 35 | * @dev allows a minter to burn some of its own tokens 36 | * Validates that caller is a minter and that sender is not blacklisted 37 | * amount is less than or equal to the minter's account balance 38 | * @param amount uint256 the amount of tokens to be burned 39 | */ 40 | function burn(uint256 amount) external; 41 | } 42 | -------------------------------------------------------------------------------- /src/messages/v2/AddressUtils.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Circle Internet Group, Inc. All rights reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | pragma solidity 0.7.6; 19 | 20 | /** 21 | * @title AddressUtils Library 22 | * @notice Helper functions for converting addresses to and from bytes 23 | **/ 24 | library AddressUtils { 25 | /** 26 | * @notice Converts an address to bytes32 by left-padding with zeros (alignment preserving cast.) 27 | * @param addr The address to convert to bytes32 28 | */ 29 | function toBytes32(address addr) internal pure returns (bytes32) { 30 | return bytes32(uint256(uint160(addr))); 31 | } 32 | 33 | /** 34 | * @notice Converts bytes32 to address (alignment preserving cast.) 35 | * @dev Warning: it is possible to have different input values _buf map to the same address. 36 | * For use cases where this is not acceptable, validate that the first 12 bytes of _buf are zero-padding. 37 | * @param _buf the bytes32 to convert to address 38 | */ 39 | function toAddress(bytes32 _buf) internal pure returns (address) { 40 | return address(uint160(uint256(_buf))); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/messages/v2/AddressUtilsExternal.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Circle Internet Group, Inc. All rights reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | pragma solidity 0.7.6; 19 | 20 | /** 21 | * @title AddressUtilsExternal Library 22 | * @notice Helper functions for converting addresses to and from bytes 23 | **/ 24 | library AddressUtilsExternal { 25 | /** 26 | * @notice converts address to bytes32 (alignment preserving cast.) 27 | * @param addr the address to convert to bytes32 28 | */ 29 | function addressToBytes32(address addr) external pure returns (bytes32) { 30 | return bytes32(uint256(uint160(addr))); 31 | } 32 | 33 | /** 34 | * @notice converts bytes32 to address (alignment preserving cast.) 35 | * @dev Warning: it is possible to have different input values _buf map to the same address. 36 | * For use cases where this is not acceptable, validate that the first 12 bytes of _buf are zero-padding. 37 | * @param _buf the bytes32 to convert to address 38 | */ 39 | function bytes32ToAddress(bytes32 _buf) external pure returns (address) { 40 | return address(uint160(uint256(_buf))); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /scripts/v2/DeployAddressUtilsExternal.s.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Circle Internet Group, Inc. All rights reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | pragma solidity 0.7.6; 19 | 20 | import {Script} from "forge-std/Script.sol"; 21 | import {AddressUtilsExternal} from "../../src/messages/v2/AddressUtilsExternal.sol"; 22 | import {Create2Factory} from "../../src/v2/Create2Factory.sol"; 23 | import {SALT_ADDRESS_UTILS_EXTERNAL} from "./Salts.sol"; 24 | 25 | contract DeployAddressUtilsExternalScript is Script { 26 | Create2Factory private create2Factory; 27 | 28 | function deployAddressUtilsExternalScript() 29 | private 30 | returns (address _addressUtilsExternal) 31 | { 32 | vm.startBroadcast(create2Factory.owner()); 33 | _addressUtilsExternal = create2Factory.deploy( 34 | 0, 35 | SALT_ADDRESS_UTILS_EXTERNAL, 36 | type(AddressUtilsExternal).creationCode 37 | ); 38 | vm.stopBroadcast(); 39 | } 40 | 41 | function setUp() public { 42 | create2Factory = Create2Factory( 43 | vm.envAddress("CREATE2_FACTORY_CONTRACT_ADDRESS") 44 | ); 45 | } 46 | 47 | function run() public { 48 | deployAddressUtilsExternalScript(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /docs/abis/cctp/Ownable2Step.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "anonymous": false, 4 | "inputs": [ 5 | { 6 | "indexed": true, 7 | "internalType": "address", 8 | "name": "previousOwner", 9 | "type": "address" 10 | }, 11 | { 12 | "indexed": true, 13 | "internalType": "address", 14 | "name": "newOwner", 15 | "type": "address" 16 | } 17 | ], 18 | "name": "OwnershipTransferStarted", 19 | "type": "event" 20 | }, 21 | { 22 | "anonymous": false, 23 | "inputs": [ 24 | { 25 | "indexed": true, 26 | "internalType": "address", 27 | "name": "previousOwner", 28 | "type": "address" 29 | }, 30 | { 31 | "indexed": true, 32 | "internalType": "address", 33 | "name": "newOwner", 34 | "type": "address" 35 | } 36 | ], 37 | "name": "OwnershipTransferred", 38 | "type": "event" 39 | }, 40 | { 41 | "inputs": [], 42 | "name": "acceptOwnership", 43 | "outputs": [], 44 | "stateMutability": "nonpayable", 45 | "type": "function" 46 | }, 47 | { 48 | "inputs": [], 49 | "name": "owner", 50 | "outputs": [ 51 | { 52 | "internalType": "address", 53 | "name": "", 54 | "type": "address" 55 | } 56 | ], 57 | "stateMutability": "view", 58 | "type": "function" 59 | }, 60 | { 61 | "inputs": [], 62 | "name": "pendingOwner", 63 | "outputs": [ 64 | { 65 | "internalType": "address", 66 | "name": "", 67 | "type": "address" 68 | } 69 | ], 70 | "stateMutability": "view", 71 | "type": "function" 72 | }, 73 | { 74 | "inputs": [ 75 | { 76 | "internalType": "address", 77 | "name": "newOwner", 78 | "type": "address" 79 | } 80 | ], 81 | "name": "transferOwnership", 82 | "outputs": [], 83 | "stateMutability": "nonpayable", 84 | "type": "function" 85 | } 86 | ] -------------------------------------------------------------------------------- /src/interfaces/v2/ITokenMinterV2.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Circle Internet Group, Inc. All rights reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | pragma solidity 0.7.6; 19 | 20 | import {ITokenMinter} from "../ITokenMinter.sol"; 21 | 22 | /** 23 | * @title ITokenMinterV2 24 | * @notice Interface for a minter of tokens that are mintable, burnable, and interchangeable 25 | * across domains. 26 | */ 27 | interface ITokenMinterV2 is ITokenMinter { 28 | /** 29 | * @notice Mints to multiple recipients amounts of tokens corresponding to the 30 | * given (`sourceDomain`, `burnToken`) pair. 31 | * @param sourceDomain Source domain where `burnToken` was burned. 32 | * @param burnToken Burned token address as bytes32. 33 | * @param recipientOne Address to receive `amountOne` of minted tokens 34 | * @param recipientTwo Address to receive `amountTwo` of minted tokens 35 | * @param amountOne Amount of tokens to mint to `recipientOne` 36 | * @param amountTwo Amount of tokens to mint to `recipientTwo` 37 | * @return mintToken Address of the token that was minted, corresponding to the (`sourceDomain`, `burnToken`) pair 38 | */ 39 | function mint( 40 | uint32 sourceDomain, 41 | bytes32 burnToken, 42 | address recipientOne, 43 | address recipientTwo, 44 | uint256 amountOne, 45 | uint256 amountTwo 46 | ) external returns (address); 47 | } 48 | -------------------------------------------------------------------------------- /src/interfaces/v2/IRelayerV2.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Circle Internet Group, Inc. All rights reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | pragma solidity 0.7.6; 19 | 20 | /** 21 | * @title IRelayerV2 22 | * @notice Sends messages from the source domain to the destination domain 23 | */ 24 | interface IRelayerV2 { 25 | /** 26 | * @notice Sends an outgoing message from the source domain. 27 | * @dev Emits a `MessageSent` event with message information. 28 | * WARNING: if the `destinationCaller` does not represent a valid address as bytes32, then it will not be possible 29 | * to broadcast the message on the destination domain. If set to bytes32(0), anyone will be able to broadcast it. 30 | * This is an advanced feature, and using bytes32(0) should be preferred for use cases where a specific destination caller is not required. 31 | * @param destinationDomain Domain of destination chain 32 | * @param recipient Address of message recipient on destination domain as bytes32 33 | * @param destinationCaller Allowed caller on destination domain (see above WARNING). 34 | * @param minFinalityThreshold Minimum finality threshold at which the message must be attested to. 35 | * @param messageBody Content of the message, as raw bytes 36 | */ 37 | function sendMessage( 38 | uint32 destinationDomain, 39 | bytes32 recipient, 40 | bytes32 destinationCaller, 41 | uint32 minFinalityThreshold, 42 | bytes calldata messageBody 43 | ) external; 44 | } 45 | -------------------------------------------------------------------------------- /test/roles/Ownable2Step.t.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, Circle Internet Financial Limited. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | pragma solidity 0.7.6; 17 | pragma abicoder v2; 18 | 19 | import "../../lib/forge-std/src/Test.sol"; 20 | import "../../src/roles/Ownable2Step.sol"; 21 | import "../../src/TokenMinter.sol"; 22 | 23 | /** 24 | * @dev Negative unit tests of third party OZ contract, Ownable2Step. 25 | * (Positive tests for transferOwnership and acceptOwnership are covered in 26 | * MessageTransmitter.t.sol, TokenMessenger.t.sol, and TokenMinter.t.sol.) 27 | */ 28 | contract Ownable2StepTest is Test { 29 | event OwnershipTransferStarted( 30 | address indexed previousOwner, 31 | address indexed newOwner 32 | ); 33 | 34 | event OwnershipTransferred( 35 | address indexed previousOwner, 36 | address indexed newOwner 37 | ); 38 | 39 | address initialOwner = vm.addr(1505); 40 | 41 | Ownable2Step ownable; 42 | 43 | function setUp() public { 44 | // (arbitrary token controller param needed for instantiation) 45 | vm.prank(initialOwner); 46 | ownable = new TokenMinter(initialOwner); 47 | assertEq(ownable.owner(), initialOwner); 48 | } 49 | 50 | function testTransferOwnership_onlyOwner(address _wrongOwner) public { 51 | address _newOwner = vm.addr(1506); 52 | vm.assume(_wrongOwner != initialOwner); 53 | vm.prank(_wrongOwner); 54 | vm.expectRevert("Ownable: caller is not the owner"); 55 | ownable.transferOwnership(_newOwner); 56 | } 57 | 58 | function testAcceptOwnership_onlyNewOwner() public { 59 | vm.expectRevert("Ownable2Step: caller is not the new owner"); 60 | ownable.acceptOwnership(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/interfaces/v2/IMessageHandlerV2.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Circle Internet Group, Inc. All rights reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | pragma solidity 0.7.6; 19 | 20 | /** 21 | * @title IMessageHandlerV2 22 | * @notice Handles messages on the destination domain, forwarded from 23 | * an IReceiverV2. 24 | */ 25 | interface IMessageHandlerV2 { 26 | /** 27 | * @notice Handles an incoming finalized message from an IReceiverV2 28 | * @dev Finalized messages have finality threshold values greater than or equal to 2000 29 | * @param sourceDomain The source domain of the message 30 | * @param sender The sender of the message 31 | * @param finalityThresholdExecuted the finality threshold at which the message was attested to 32 | * @param messageBody The raw bytes of the message body 33 | * @return success True, if successful; false, if not. 34 | */ 35 | function handleReceiveFinalizedMessage( 36 | uint32 sourceDomain, 37 | bytes32 sender, 38 | uint32 finalityThresholdExecuted, 39 | bytes calldata messageBody 40 | ) external returns (bool); 41 | 42 | /** 43 | * @notice Handles an incoming unfinalized message from an IReceiverV2 44 | * @dev Unfinalized messages have finality threshold values less than 2000 45 | * @param sourceDomain The source domain of the message 46 | * @param sender The sender of the message 47 | * @param finalityThresholdExecuted The finality threshold at which the message was attested to 48 | * @param messageBody The raw bytes of the message body 49 | * @return success True, if successful; false, if not. 50 | */ 51 | function handleReceiveUnfinalizedMessage( 52 | uint32 sourceDomain, 53 | bytes32 sender, 54 | uint32 finalityThresholdExecuted, 55 | bytes calldata messageBody 56 | ) external returns (bool); 57 | } 58 | -------------------------------------------------------------------------------- /test/scripts/v2/DeployImplementationsV2.t.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024, Circle Internet Financial Limited. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | pragma solidity 0.7.6; 17 | pragma abicoder v2; 18 | 19 | import {ScriptV2TestUtils} from "./ScriptV2TestUtils.sol"; 20 | import {DeployImplementationsV2Script} from "../../../scripts/v2/DeployImplementationsV2.s.sol"; 21 | import {MessageTransmitterV2} from "../../../src/v2/MessageTransmitterV2.sol"; 22 | import {TokenMessengerV2} from "../../../src/v2/TokenMessengerV2.sol"; 23 | import {SALT_TOKEN_MINTER} from "../../../scripts/v2/Salts.sol"; 24 | import {TokenMinterV2} from "../../../src/v2/TokenMinterV2.sol"; 25 | 26 | contract DeployImplementationsV2Test is ScriptV2TestUtils { 27 | DeployImplementationsV2Script deployImplementationsV2Script; 28 | 29 | function setUp() public { 30 | _deployCreate2Factory(); 31 | _deployImplementations(); 32 | deployImplementationsV2Script = new DeployImplementationsV2Script(); 33 | } 34 | 35 | function testDeployImplementationsV2() public { 36 | // MessageTransmitterV2 37 | assertEq(messageTransmitterV2Impl.localDomain(), uint256(sourceDomain)); 38 | assertEq(messageTransmitterV2Impl.version(), uint256(_version)); 39 | 40 | // TokenMinterV2 41 | address predictedTokenMinterAddress = create2Factory.computeAddress( 42 | SALT_TOKEN_MINTER, 43 | keccak256( 44 | abi.encodePacked( 45 | type(TokenMinterV2).creationCode, 46 | abi.encode(create2Factory) 47 | ) 48 | ) 49 | ); 50 | assertEq(address(tokenMinterV2), predictedTokenMinterAddress); 51 | assertEq(tokenMinterV2.tokenController(), deployer); 52 | assertEq(tokenMinterV2.owner(), deployer); 53 | 54 | // TokenMessengerV2 55 | assertEq( 56 | address(tokenMessengerV2Impl.localMessageTransmitter()), 57 | address(expectedMessageTransmitterV2ProxyAddress) 58 | ); 59 | assertEq( 60 | tokenMessengerV2Impl.messageBodyVersion(), 61 | uint256(_messageBodyVersion) 62 | ); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | on: 3 | pull_request: 4 | push: 5 | branches: [master] 6 | jobs: 7 | lint-and-test: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Check out repository code 11 | uses: actions/checkout@v4 12 | with: 13 | submodules: 'true' 14 | 15 | - name: Setup Python 16 | uses: actions/setup-python@v5 17 | with: 18 | python-version: '3.10' 19 | 20 | - name: Install Node 21 | uses: actions/setup-node@v4 22 | 23 | - name: Install Solhint 24 | run: npm i 25 | 26 | - name: Run Linter 27 | run: npm run lint 28 | 29 | - name: Build Forge Image 30 | run: make build 31 | 32 | - name: Run Unit Tests 33 | run: make test 34 | 35 | - name: Run Integration Tests 36 | run: make anvil-test 37 | 38 | - name: Run v2 Integration Tests 39 | run: make anvil-test-v2 40 | 41 | analyze-message-transmitter: 42 | runs-on: ubuntu-latest 43 | steps: 44 | - name: Check out repository code 45 | uses: actions/checkout@v4 46 | with: 47 | submodules: 'true' 48 | 49 | - name: Set up Python 50 | uses: actions/setup-python@v5 51 | with: 52 | python-version: '3.10' 53 | 54 | - name: Run Static Analysis on Message Transmitter 55 | run: make analyze-message-transmitter 56 | 57 | analyze-message-transmitter-v2: 58 | runs-on: ubuntu-latest 59 | steps: 60 | - name: Check out repository code 61 | uses: actions/checkout@v4 62 | with: 63 | submodules: 'true' 64 | 65 | - name: Set up Python 66 | uses: actions/setup-python@v5 67 | with: 68 | python-version: '3.10' 69 | 70 | - name: Run Static Analysis on Message Transmitter V2 71 | run: make analyze-message-transmitter-v2 72 | 73 | analyze-token-messenger-minter: 74 | runs-on: ubuntu-latest 75 | steps: 76 | - name: Check out repository code 77 | uses: actions/checkout@v4 78 | with: 79 | submodules: 'true' 80 | 81 | - name: Set up Python 82 | uses: actions/setup-python@v5 83 | with: 84 | python-version: '3.10' 85 | 86 | - name: Run Static Analysis on Token Messenger Minter 87 | run: make analyze-token-messenger-minter 88 | 89 | scan: 90 | needs: lint-and-test 91 | if: github.event_name == 'pull_request' 92 | uses: circlefin/circle-public-github-workflows/.github/workflows/pr-scan.yaml@v1 93 | 94 | release-sbom: 95 | needs: lint-and-test 96 | if: github.event_name == 'push' 97 | uses: circlefin/circle-public-github-workflows/.github/workflows/attach-release-assets.yaml@v1 98 | -------------------------------------------------------------------------------- /test/scripts/v2/PredictCreate2Deployments.t.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Circle Internet Group, Inc. All rights reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | pragma solidity 0.7.6; 19 | pragma abicoder v2; 20 | 21 | import {ScriptV2TestUtils} from "./ScriptV2TestUtils.sol"; 22 | import {PredictCreate2Deployments} from "../../../scripts/v2/PredictCreate2Deployments.s.sol"; 23 | 24 | contract PredictCreate2DeploymentsTest is ScriptV2TestUtils { 25 | PredictCreate2Deployments predictDeployments = 26 | new PredictCreate2Deployments(); 27 | 28 | function setUp() public { 29 | // Only need to deploy the contracts since we're interested only in their addresses 30 | _deployCreate2Factory(); 31 | _deployImplementations(); 32 | _deployProxies(); 33 | } 34 | 35 | function testPredictMessageTransmitterV2ImplAddress() public { 36 | address _predicted = predictDeployments.messageTransmitterV2Impl( 37 | address(create2Factory), 38 | sourceDomain, 39 | _version 40 | ); 41 | assertEq(_predicted, address(messageTransmitterV2Impl)); 42 | } 43 | 44 | function testPredictTokenMessengerV2ImplAddress() public { 45 | address _predicted = predictDeployments.tokenMessengerV2Impl( 46 | address(create2Factory), 47 | _messageBodyVersion 48 | ); 49 | assertEq(_predicted, address(tokenMessengerV2Impl)); 50 | } 51 | 52 | function testPredictMessageTransmitterV2ProxyAddress() public { 53 | address _predicted = predictDeployments.messageTransmitterV2Proxy( 54 | address(create2Factory) 55 | ); 56 | assertEq(_predicted, address(messageTransmitterV2)); 57 | } 58 | 59 | function testPredictTokenMessengerV2ProxyAddress() public { 60 | address _predicted = predictDeployments.tokenMessengerV2Proxy( 61 | address(create2Factory) 62 | ); 63 | assertEq(_predicted, address(tokenMessengerV2)); 64 | } 65 | 66 | function testPredictTokenMinterV2Address() public { 67 | address _predicted = predictDeployments.tokenMinterV2( 68 | address(create2Factory) 69 | ); 70 | assertEq(_predicted, address(tokenMinterV2)); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/interfaces/ITokenMinter.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, Circle Internet Financial Limited. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | pragma solidity 0.7.6; 17 | 18 | /** 19 | * @title ITokenMinter 20 | * @notice interface for minter of tokens that are mintable, burnable, and interchangeable 21 | * across domains. 22 | */ 23 | interface ITokenMinter { 24 | /** 25 | * @notice Mints `amount` of local tokens corresponding to the 26 | * given (`sourceDomain`, `burnToken`) pair, to `to` address. 27 | * @dev reverts if the (`sourceDomain`, `burnToken`) pair does not 28 | * map to a nonzero local token address. This mapping can be queried using 29 | * getLocalToken(). 30 | * @param sourceDomain Source domain where `burnToken` was burned. 31 | * @param burnToken Burned token address as bytes32. 32 | * @param to Address to receive minted tokens, corresponding to `burnToken`, 33 | * on this domain. 34 | * @param amount Amount of tokens to mint. Must be less than or equal 35 | * to the minterAllowance of this TokenMinter for given `_mintToken`. 36 | * @return mintToken token minted. 37 | */ 38 | function mint( 39 | uint32 sourceDomain, 40 | bytes32 burnToken, 41 | address to, 42 | uint256 amount 43 | ) external returns (address mintToken); 44 | 45 | /** 46 | * @notice Burn tokens owned by this ITokenMinter. 47 | * @param burnToken burnable token. 48 | * @param amount amount of tokens to burn. Must be less than or equal to this ITokenMinter's 49 | * account balance of the given `_burnToken`. 50 | */ 51 | function burn(address burnToken, uint256 amount) external; 52 | 53 | /** 54 | * @notice Get the local token associated with the given remote domain and token. 55 | * @param remoteDomain Remote domain 56 | * @param remoteToken Remote token 57 | * @return local token address 58 | */ 59 | function getLocalToken(uint32 remoteDomain, bytes32 remoteToken) 60 | external 61 | view 62 | returns (address); 63 | 64 | /** 65 | * @notice Set the token controller of this ITokenMinter. Token controller 66 | * is responsible for mapping local tokens to remote tokens, and managing 67 | * token-specific limits 68 | * @param newTokenController new token controller address 69 | */ 70 | function setTokenController(address newTokenController) external; 71 | } 72 | -------------------------------------------------------------------------------- /src/roles/Pausable.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, Circle Internet Financial Limited. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | pragma solidity 0.7.6; 17 | 18 | import "./Ownable2Step.sol"; 19 | 20 | /** 21 | * @notice Base contract which allows children to implement an emergency stop 22 | * mechanism 23 | * @dev Forked from https://github.com/centrehq/centre-tokens/blob/0d3cab14ebd133a83fc834dbd48d0468bdf0b391/contracts/v1/Pausable.sol 24 | * Modifications: 25 | * 1. Update Solidity version from 0.6.12 to 0.7.6 (8/23/2022) 26 | * 2. Change pauser visibility to private, declare external getter (11/19/22) 27 | * 3. Add internal _updatePauser (10/8/2024) 28 | */ 29 | contract Pausable is Ownable2Step { 30 | event Pause(); 31 | event Unpause(); 32 | event PauserChanged(address indexed newAddress); 33 | 34 | address private _pauser; 35 | bool public paused = false; 36 | 37 | /** 38 | * @dev Modifier to make a function callable only when the contract is not paused. 39 | */ 40 | modifier whenNotPaused() { 41 | require(!paused, "Pausable: paused"); 42 | _; 43 | } 44 | 45 | /** 46 | * @dev throws if called by any account other than the pauser 47 | */ 48 | modifier onlyPauser() { 49 | require(msg.sender == _pauser, "Pausable: caller is not the pauser"); 50 | _; 51 | } 52 | 53 | /** 54 | * @notice Returns current pauser 55 | * @return Pauser's address 56 | */ 57 | function pauser() external view returns (address) { 58 | return _pauser; 59 | } 60 | 61 | /** 62 | * @dev called by the owner to pause, triggers stopped state 63 | */ 64 | function pause() external onlyPauser { 65 | paused = true; 66 | emit Pause(); 67 | } 68 | 69 | /** 70 | * @dev called by the owner to unpause, returns to normal state 71 | */ 72 | function unpause() external onlyPauser { 73 | paused = false; 74 | emit Unpause(); 75 | } 76 | 77 | /** 78 | * @dev update the pauser role 79 | */ 80 | function updatePauser(address _newPauser) external onlyOwner { 81 | _updatePauser(_newPauser); 82 | } 83 | 84 | /** 85 | * @dev update the pauser role 86 | */ 87 | function _updatePauser(address _newPauser) internal { 88 | require( 89 | _newPauser != address(0), 90 | "Pausable: new pauser is the zero address" 91 | ); 92 | _pauser = _newPauser; 93 | emit PauserChanged(_pauser); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/roles/Rescuable.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, Circle Internet Financial Limited. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | pragma solidity 0.7.6; 17 | 18 | import "./Ownable2Step.sol"; 19 | import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 20 | import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; 21 | 22 | /** 23 | * @notice Base contract which allows children to rescue ERC20 locked in their contract. 24 | * @dev Forked from https://github.com/centrehq/centre-tokens/blob/0d3cab14ebd133a83fc834dbd48d0468bdf0b391/contracts/v1.1/Rescuable.sol 25 | * Modifications: 26 | * 1. Update Solidity version from 0.6.12 to 0.7.6 (8/23/2022) 27 | * 2. Add internal _updateRescuer (10/8/2024) 28 | */ 29 | contract Rescuable is Ownable2Step { 30 | using SafeERC20 for IERC20; 31 | 32 | address private _rescuer; 33 | 34 | event RescuerChanged(address indexed newRescuer); 35 | 36 | /** 37 | * @notice Returns current rescuer 38 | * @return Rescuer's address 39 | */ 40 | function rescuer() external view returns (address) { 41 | return _rescuer; 42 | } 43 | 44 | /** 45 | * @notice Revert if called by any account other than the rescuer. 46 | */ 47 | modifier onlyRescuer() { 48 | require(msg.sender == _rescuer, "Rescuable: caller is not the rescuer"); 49 | _; 50 | } 51 | 52 | /** 53 | * @notice Rescue ERC20 tokens locked up in this contract. 54 | * @param tokenContract ERC20 token contract address 55 | * @param to Recipient address 56 | * @param amount Amount to withdraw 57 | */ 58 | function rescueERC20( 59 | IERC20 tokenContract, 60 | address to, 61 | uint256 amount 62 | ) external onlyRescuer { 63 | tokenContract.safeTransfer(to, amount); 64 | } 65 | 66 | /** 67 | * @notice Assign the rescuer role to a given address. 68 | * @param newRescuer New rescuer's address 69 | */ 70 | function updateRescuer(address newRescuer) external onlyOwner { 71 | _updateRescuer(newRescuer); 72 | } 73 | 74 | /** 75 | * @notice Assign the rescuer role to a given address. 76 | * @param newRescuer New rescuer's address 77 | */ 78 | function _updateRescuer(address newRescuer) internal { 79 | require( 80 | newRescuer != address(0), 81 | "Rescuable: new rescuer is the zero address" 82 | ); 83 | _rescuer = newRescuer; 84 | emit RescuerChanged(newRescuer); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/v2/Create2Factory.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024, Circle Internet Financial Limited. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | pragma solidity 0.7.6; 19 | pragma abicoder v2; 20 | 21 | import {Address} from "@openzeppelin/contracts/utils/Address.sol"; 22 | import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; 23 | import {Ownable} from "../roles/Ownable.sol"; 24 | 25 | /** 26 | * @title Create2Factory 27 | * @notice Contract used for deterministic contract deployments across chains. 28 | */ 29 | contract Create2Factory is Ownable { 30 | /** 31 | * @notice Deploys the contract. 32 | * @param amount Amount of native token to seed the deployment 33 | * @param salt A unique identifier 34 | * @param bytecode The contract bytecode to deploy 35 | * @return addr The deployed address 36 | */ 37 | function deploy( 38 | uint256 amount, 39 | bytes32 salt, 40 | bytes calldata bytecode 41 | ) external payable onlyOwner returns (address addr) { 42 | // Deploy deterministically 43 | addr = Create2.deploy(amount, salt, bytecode); 44 | } 45 | 46 | /** 47 | * @notice Deploys the contract and calls into it. 48 | * @param amount Amount of native token to seed the deployment 49 | * @param salt A unique identifier 50 | * @param bytecode The contract bytecode to deploy 51 | * @param data The data to call the implementation with 52 | * @return addr The deployed address 53 | */ 54 | function deployAndMultiCall( 55 | uint256 amount, 56 | bytes32 salt, 57 | bytes calldata bytecode, 58 | bytes[] calldata data 59 | ) external payable onlyOwner returns (address addr) { 60 | // Deploy deterministically 61 | addr = Create2.deploy(amount, salt, bytecode); 62 | 63 | uint256 dataLength = data.length; 64 | for (uint256 i = 0; i < dataLength; ++i) { 65 | Address.functionCall(addr, data[i]); 66 | } 67 | } 68 | 69 | /** 70 | * @notice A helper function for predicting a deterministic address. 71 | * @param salt The unique identifier 72 | * @param bytecodeHash The keccak256 hash of the deployment bytecode. 73 | * @return addr The deterministic address 74 | */ 75 | function computeAddress( 76 | bytes32 salt, 77 | bytes32 bytecodeHash 78 | ) external view returns (address addr) { 79 | addr = Create2.computeAddress(salt, bytecodeHash); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Quickstart: Cross-chain USDC Transfer 3 | 4 | ## Overview 5 | 6 | This example uses [web3.js](https://web3js.readthedocs.io/en/v1.8.1/getting-started.html) to transfer USDC from an address on ETH testnet to another address on AVAX testnet. 7 | 8 | ### Prerequisite 9 | The script requires [Node.js](https://nodejs.org/en/download/) installed. 10 | 11 | ## Usage 12 | 1. Install dependencies by running `npm install` 13 | 2. Create a `.env` file and add below variables to it: 14 | ```js 15 | ETH_TESTNET_RPC= 16 | AVAX_TESTNET_RPC= 17 | ETH_PRIVATE_KEY= 18 | AVAX_PRIVATE_KEY= 19 | RECIPIENT_ADDRESS= 20 | AMOUNT= 21 | ``` 22 | 3. Run the script by running `npm run start` 23 | 24 | ## Script Details 25 | The script has 5 steps: 26 | 1. First step approves `ETH_TOKEN_MESSENGER_CONTRACT_ADDRESS` to withdraw USDC from our eth address. 27 | ```js 28 | const approveTx = await usdcEthContract.methods.approve(ETH_TOKEN_MESSENGER_CONTRACT_ADDRESS, amount).send({gas: approveTxGas}) 29 | ``` 30 | 31 | 2. Second step executes `depositForBurn` function on `TokenMessengerContract` deployed in [Sepolia testnet](https://sepolia.etherscan.io/address/0x9f3B8679c73C2Fef8b59B4f3444d4e156fb70AA5) 32 | ```js 33 | const burnTx = await ethTokenMessengerContract.methods.depositForBurn(amount, AVAX_DESTINATION_DOMAIN, destinationAddressInBytes32, USDC_ETH_CONTRACT_ADDRESS).send(); 34 | ``` 35 | 36 | 3. Third step extracts `messageBytes` emitted by `MessageSent` event from `depositForBurn` transaction logs and hashes the retrieved `messageBytes` using `keccak256` hashing algorithm 37 | ```js 38 | const transactionReceipt = await web3.eth.getTransactionReceipt(burnTx.transactionHash); 39 | const eventTopic = web3.utils.keccak256('MessageSent(bytes)') 40 | const log = transactionReceipt.logs.find((l) => l.topics[0] === eventTopic) 41 | const messageBytes = web3.eth.abi.decodeParameters(['bytes'], log.data)[0] 42 | const messageHash = web3.utils.keccak256(messageBytes); 43 | ``` 44 | 45 | 4. Fourth step polls the attestation service to acquire signature using the `messageHash` from previous step. 46 | ```js 47 | let attestationResponse = {status: 'pending'}; 48 | while(attestationResponse.status != 'complete') { 49 | const response = await fetch(`https://iris-api-sandbox.circle.com/attestations/${messageHash}`); 50 | attestationResponse = await response.json() 51 | await new Promise(r => setTimeout(r, 2000)); 52 | } 53 | ``` 54 | 55 | 5. Last step calls `receiveMessage` function on `MessageTransmitterContract` deployed in [Avalanche Fuji Network](https://testnet.snowtrace.io/address/0xa9fb1b3009dcb79e2fe346c16a604b8fa8ae0a79) to receive USDC at AVAX address. 56 | 57 | *Note: The attestation service is rate-limited, please limit your requests to less than 1 per second.* 58 | ```js 59 | const receiveTx = await avaxMessageTransmitterContract.receiveMessage(receivingMessageBytes, signature); 60 | ``` -------------------------------------------------------------------------------- /src/roles/Ownable2Step.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, Circle Internet Financial Limited. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | pragma solidity 0.7.6; 17 | 18 | import "./Ownable.sol"; 19 | 20 | /** 21 | * @dev forked from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/7c5f6bc2c8743d83443fa46395d75f2f3f99054a/contracts/access/Ownable2Step.sol 22 | * Modifications: 23 | * 1. Update Solidity version from 0.8.0 to 0.7.6. Version 0.8.0 was used 24 | * as base because this contract was added to OZ repo after version 0.8.0. 25 | * 26 | * Contract module which provides access control mechanism, where 27 | * there is an account (an owner) that can be granted exclusive access to 28 | * specific functions. 29 | * 30 | * By default, the owner account will be the one that deploys the contract. This 31 | * can later be changed with {transferOwnership} and {acceptOwnership}. 32 | * 33 | * This module is used through inheritance. It will make available all functions 34 | * from parent (Ownable). 35 | */ 36 | abstract contract Ownable2Step is Ownable { 37 | address private _pendingOwner; 38 | 39 | event OwnershipTransferStarted( 40 | address indexed previousOwner, 41 | address indexed newOwner 42 | ); 43 | 44 | /** 45 | * @dev Returns the address of the pending owner. 46 | */ 47 | function pendingOwner() public view virtual returns (address) { 48 | return _pendingOwner; 49 | } 50 | 51 | /** 52 | * @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one. 53 | * Can only be called by the current owner. 54 | */ 55 | function transferOwnership(address newOwner) 56 | public 57 | virtual 58 | override 59 | onlyOwner 60 | { 61 | _pendingOwner = newOwner; 62 | emit OwnershipTransferStarted(owner(), newOwner); 63 | } 64 | 65 | /** 66 | * @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner. 67 | * Internal function without access restriction. 68 | */ 69 | function _transferOwnership(address newOwner) internal virtual override { 70 | delete _pendingOwner; 71 | super._transferOwnership(newOwner); 72 | } 73 | 74 | /** 75 | * @dev The new owner accepts the ownership transfer. 76 | */ 77 | function acceptOwnership() external { 78 | address sender = _msgSender(); 79 | require( 80 | pendingOwner() == sender, 81 | "Ownable2Step: caller is not the new owner" 82 | ); 83 | _transferOwnership(sender); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /docs/abis/cctp/v2.1/Create2Factory.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "function", 4 | "name": "computeAddress", 5 | "inputs": [ 6 | { 7 | "name": "salt", 8 | "type": "bytes32", 9 | "internalType": "bytes32" 10 | }, 11 | { 12 | "name": "bytecodeHash", 13 | "type": "bytes32", 14 | "internalType": "bytes32" 15 | } 16 | ], 17 | "outputs": [ 18 | { 19 | "name": "addr", 20 | "type": "address", 21 | "internalType": "address" 22 | } 23 | ], 24 | "stateMutability": "view" 25 | }, 26 | { 27 | "type": "function", 28 | "name": "deploy", 29 | "inputs": [ 30 | { 31 | "name": "amount", 32 | "type": "uint256", 33 | "internalType": "uint256" 34 | }, 35 | { 36 | "name": "salt", 37 | "type": "bytes32", 38 | "internalType": "bytes32" 39 | }, 40 | { 41 | "name": "bytecode", 42 | "type": "bytes", 43 | "internalType": "bytes" 44 | } 45 | ], 46 | "outputs": [ 47 | { 48 | "name": "addr", 49 | "type": "address", 50 | "internalType": "address" 51 | } 52 | ], 53 | "stateMutability": "payable" 54 | }, 55 | { 56 | "type": "function", 57 | "name": "deployAndMultiCall", 58 | "inputs": [ 59 | { 60 | "name": "amount", 61 | "type": "uint256", 62 | "internalType": "uint256" 63 | }, 64 | { 65 | "name": "salt", 66 | "type": "bytes32", 67 | "internalType": "bytes32" 68 | }, 69 | { 70 | "name": "bytecode", 71 | "type": "bytes", 72 | "internalType": "bytes" 73 | }, 74 | { 75 | "name": "data", 76 | "type": "bytes[]", 77 | "internalType": "bytes[]" 78 | } 79 | ], 80 | "outputs": [ 81 | { 82 | "name": "addr", 83 | "type": "address", 84 | "internalType": "address" 85 | } 86 | ], 87 | "stateMutability": "payable" 88 | }, 89 | { 90 | "type": "function", 91 | "name": "owner", 92 | "inputs": [], 93 | "outputs": [ 94 | { 95 | "name": "", 96 | "type": "address", 97 | "internalType": "address" 98 | } 99 | ], 100 | "stateMutability": "view" 101 | }, 102 | { 103 | "type": "function", 104 | "name": "transferOwnership", 105 | "inputs": [ 106 | { 107 | "name": "newOwner", 108 | "type": "address", 109 | "internalType": "address" 110 | } 111 | ], 112 | "outputs": [], 113 | "stateMutability": "nonpayable" 114 | }, 115 | { 116 | "type": "event", 117 | "name": "OwnershipTransferred", 118 | "inputs": [ 119 | { 120 | "name": "previousOwner", 121 | "type": "address", 122 | "indexed": true, 123 | "internalType": "address" 124 | }, 125 | { 126 | "name": "newOwner", 127 | "type": "address", 128 | "indexed": true, 129 | "internalType": "address" 130 | } 131 | ], 132 | "anonymous": false 133 | } 134 | ] -------------------------------------------------------------------------------- /src/interfaces/IRelayer.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, Circle Internet Financial Limited. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | pragma solidity 0.7.6; 17 | 18 | /** 19 | * @title IRelayer 20 | * @notice Sends messages from source domain to destination domain 21 | */ 22 | interface IRelayer { 23 | /** 24 | * @notice Sends an outgoing message from the source domain. 25 | * @dev Increment nonce, format the message, and emit `MessageSent` event with message information. 26 | * @param destinationDomain Domain of destination chain 27 | * @param recipient Address of message recipient on destination domain as bytes32 28 | * @param messageBody Raw bytes content of message 29 | * @return nonce reserved by message 30 | */ 31 | function sendMessage( 32 | uint32 destinationDomain, 33 | bytes32 recipient, 34 | bytes calldata messageBody 35 | ) external returns (uint64); 36 | 37 | /** 38 | * @notice Sends an outgoing message from the source domain, with a specified caller on the 39 | * destination domain. 40 | * @dev Increment nonce, format the message, and emit `MessageSent` event with message information. 41 | * WARNING: if the `destinationCaller` does not represent a valid address as bytes32, then it will not be possible 42 | * to broadcast the message on the destination domain. This is an advanced feature, and the standard 43 | * sendMessage() should be preferred for use cases where a specific destination caller is not required. 44 | * @param destinationDomain Domain of destination chain 45 | * @param recipient Address of message recipient on destination domain as bytes32 46 | * @param destinationCaller caller on the destination domain, as bytes32 47 | * @param messageBody Raw bytes content of message 48 | * @return nonce reserved by message 49 | */ 50 | function sendMessageWithCaller( 51 | uint32 destinationDomain, 52 | bytes32 recipient, 53 | bytes32 destinationCaller, 54 | bytes calldata messageBody 55 | ) external returns (uint64); 56 | 57 | /** 58 | * @notice Replace a message with a new message body and/or destination caller. 59 | * @dev The `originalAttestation` must be a valid attestation of `originalMessage`. 60 | * @param originalMessage original message to replace 61 | * @param originalAttestation attestation of `originalMessage` 62 | * @param newMessageBody new message body of replaced message 63 | * @param newDestinationCaller the new destination caller 64 | */ 65 | function replaceMessage( 66 | bytes calldata originalMessage, 67 | bytes calldata originalAttestation, 68 | bytes calldata newMessageBody, 69 | bytes32 newDestinationCaller 70 | ) external; 71 | } 72 | -------------------------------------------------------------------------------- /test/mocks/MockReentrantCaller.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, Circle Internet Financial Limited. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | pragma solidity 0.7.6; 17 | 18 | import "../../src/interfaces/IMessageHandler.sol"; 19 | import "../../src/interfaces/IReceiver.sol"; 20 | import "../../src/messages/Message.sol"; 21 | import "@openzeppelin/contracts/utils/Address.sol"; 22 | 23 | contract MockReentrantCaller is IMessageHandler { 24 | bytes internal message; 25 | bytes internal signature; 26 | 27 | // ============ Constructor ============ 28 | constructor() {} 29 | 30 | function setMessageAndSignature( 31 | bytes memory _message, 32 | bytes memory _signature 33 | ) public { 34 | message = _message; 35 | signature = _signature; 36 | } 37 | 38 | function handleReceiveMessage( 39 | uint32 _sourceDomain, 40 | bytes32 _sender, 41 | bytes memory _messageBody 42 | ) public override returns (bool) { 43 | // revert if _messageBody is 'revert', otherwise do nothing 44 | require( 45 | keccak256(_messageBody) != keccak256(bytes("revert")), 46 | "mock revert" 47 | ); 48 | 49 | // if message body is 'reenter', call receiveMessage on caller 50 | if (keccak256(_messageBody) == keccak256(bytes("reenter"))) { 51 | bytes memory data = abi.encodeWithSelector( 52 | bytes4(keccak256("receiveMessage(bytes,bytes)")), 53 | message, 54 | signature 55 | ); 56 | 57 | (bool success, bytes memory returnData) = msg.sender.call(data); 58 | 59 | // Check inner error message, and log separate error if it matches expectation. 60 | // (This allows tests to ensure that the error is logged from the re-entrant call.) 61 | if (stringEquals(_getRevertMsg(returnData), "Nonce already used")) { 62 | revert("Re-entrant call failed due to reused nonce"); 63 | } 64 | } 65 | } 66 | 67 | // source: https://ethereum.stackexchange.com/a/83577 68 | function _getRevertMsg( 69 | bytes memory _returnData 70 | ) internal pure returns (string memory) { 71 | // If the _res length is less than 68, then the transaction failed silently (without a revert message) 72 | if (_returnData.length < 68) return "Transaction reverted silently"; 73 | 74 | assembly { 75 | // Slice the sighash. 76 | _returnData := add(_returnData, 0x04) 77 | } 78 | return abi.decode(_returnData, (string)); // All that remains is the revert string 79 | } 80 | 81 | function stringEquals( 82 | string memory a, 83 | string memory b 84 | ) internal pure returns (bool) { 85 | return (keccak256(abi.encodePacked((a))) == 86 | keccak256(abi.encodePacked((b)))); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/v2/TokenMinterV2.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Circle Internet Group, Inc. All rights reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | pragma solidity 0.7.6; 19 | 20 | import {TokenMinter} from "../TokenMinter.sol"; 21 | import {IMintBurnToken} from "../interfaces/IMintBurnToken.sol"; 22 | import {ITokenMinterV2} from "../interfaces/v2/ITokenMinterV2.sol"; 23 | 24 | /** 25 | * @title TokenMinterV2 26 | * @notice Token Minter and Burner 27 | * @dev Maintains registry of local mintable tokens and corresponding tokens on remote domains. 28 | * This registry can be used by caller to determine which token on local domain to mint for a 29 | * burned token on a remote domain, and vice versa. 30 | * It is assumed that local and remote tokens are fungible at a constant 1:1 exchange rate. 31 | */ 32 | contract TokenMinterV2 is ITokenMinterV2, TokenMinter { 33 | // ============ Constructor ============ 34 | /** 35 | * @param _tokenController Token controller address 36 | */ 37 | constructor(address _tokenController) TokenMinter(_tokenController) {} 38 | 39 | // ============ External Functions ============ 40 | /** 41 | * @notice Mints to multiple recipients amounts of local tokens corresponding to the 42 | * given (`sourceDomain`, `burnToken`) pair. 43 | * @dev reverts if the (`sourceDomain`, `burnToken`) pair does not 44 | * map to a nonzero local token address. This mapping can be queried using 45 | * getLocalToken(). 46 | * @param sourceDomain Source domain where `burnToken` was burned. 47 | * @param burnToken Burned token address as bytes32. 48 | * @param recipientOne Address to receive `amountOne` of minted tokens 49 | * @param recipientTwo Address to receive `amountTwo` of minted tokens 50 | * @param amountOne Amount of tokens to mint to `recipientOne` 51 | * @param amountTwo Amount of tokens to mint to `recipientTwo` 52 | * @return mintToken token minted. 53 | */ 54 | function mint( 55 | uint32 sourceDomain, 56 | bytes32 burnToken, 57 | address recipientOne, 58 | address recipientTwo, 59 | uint256 amountOne, 60 | uint256 amountTwo 61 | ) 62 | external 63 | override 64 | whenNotPaused 65 | onlyLocalTokenMessenger 66 | returns (address) 67 | { 68 | address _mintToken = _getLocalToken(sourceDomain, burnToken); 69 | require(_mintToken != address(0), "Mint token not supported"); 70 | IMintBurnToken _token = IMintBurnToken(_mintToken); 71 | 72 | require( 73 | _token.mint(recipientOne, amountOne), 74 | "First mint operation failed" 75 | ); 76 | 77 | require( 78 | _token.mint(recipientTwo, amountTwo), 79 | "Second mint operation failed" 80 | ); 81 | 82 | return _mintToken; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/roles/Ownable.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, Circle Internet Financial Limited. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | pragma solidity 0.7.6; 17 | 18 | import "@openzeppelin/contracts/utils/Context.sol"; 19 | 20 | /** 21 | * @dev forked from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/7c5f6bc2c8743d83443fa46395d75f2f3f99054a/contracts/access/Ownable.sol 22 | * Modifications: 23 | * 1. Update Solidity version from 0.8.0 to 0.7.6 (11/9/2022). (v8 was used 24 | * as base because it includes internal _transferOwnership method.) 25 | * 2. Remove renounceOwnership function 26 | * 27 | * Description 28 | * Contract module which provides a basic access control mechanism, where 29 | * there is an account (an owner) that can be granted exclusive access to 30 | * specific functions. 31 | * 32 | * By default, the owner account will be the one that deploys the contract. This 33 | * can later be changed with {transferOwnership}. 34 | * 35 | * This module is used through inheritance. It will make available the modifier 36 | * `onlyOwner`, which can be applied to your functions to restrict their use to 37 | * the owner. 38 | */ 39 | abstract contract Ownable is Context { 40 | address private _owner; 41 | 42 | event OwnershipTransferred( 43 | address indexed previousOwner, 44 | address indexed newOwner 45 | ); 46 | 47 | /** 48 | * @dev Initializes the contract setting the deployer as the initial owner. 49 | */ 50 | constructor() { 51 | _transferOwnership(_msgSender()); 52 | } 53 | 54 | /** 55 | * @dev Throws if called by any account other than the owner. 56 | */ 57 | modifier onlyOwner() { 58 | _checkOwner(); 59 | _; 60 | } 61 | 62 | /** 63 | * @dev Returns the address of the current owner. 64 | */ 65 | function owner() public view virtual returns (address) { 66 | return _owner; 67 | } 68 | 69 | /** 70 | * @dev Throws if the sender is not the owner. 71 | */ 72 | function _checkOwner() internal view virtual { 73 | require(owner() == _msgSender(), "Ownable: caller is not the owner"); 74 | } 75 | 76 | /** 77 | * @dev Transfers ownership of the contract to a new account (`newOwner`). 78 | * Can only be called by the current owner. 79 | */ 80 | function transferOwnership(address newOwner) public virtual onlyOwner { 81 | require( 82 | newOwner != address(0), 83 | "Ownable: new owner is the zero address" 84 | ); 85 | _transferOwnership(newOwner); 86 | } 87 | 88 | /** 89 | * @dev Transfers ownership of the contract to a new account (`newOwner`). 90 | * Internal function without access restriction. 91 | */ 92 | function _transferOwnership(address newOwner) internal virtual { 93 | address oldOwner = _owner; 94 | _owner = newOwner; 95 | emit OwnershipTransferred(oldOwner, newOwner); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /docs/abis/cctp/Rescuable.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "anonymous": false, 4 | "inputs": [ 5 | { 6 | "indexed": true, 7 | "internalType": "address", 8 | "name": "previousOwner", 9 | "type": "address" 10 | }, 11 | { 12 | "indexed": true, 13 | "internalType": "address", 14 | "name": "newOwner", 15 | "type": "address" 16 | } 17 | ], 18 | "name": "OwnershipTransferStarted", 19 | "type": "event" 20 | }, 21 | { 22 | "anonymous": false, 23 | "inputs": [ 24 | { 25 | "indexed": true, 26 | "internalType": "address", 27 | "name": "previousOwner", 28 | "type": "address" 29 | }, 30 | { 31 | "indexed": true, 32 | "internalType": "address", 33 | "name": "newOwner", 34 | "type": "address" 35 | } 36 | ], 37 | "name": "OwnershipTransferred", 38 | "type": "event" 39 | }, 40 | { 41 | "anonymous": false, 42 | "inputs": [ 43 | { 44 | "indexed": true, 45 | "internalType": "address", 46 | "name": "newRescuer", 47 | "type": "address" 48 | } 49 | ], 50 | "name": "RescuerChanged", 51 | "type": "event" 52 | }, 53 | { 54 | "inputs": [], 55 | "name": "acceptOwnership", 56 | "outputs": [], 57 | "stateMutability": "nonpayable", 58 | "type": "function" 59 | }, 60 | { 61 | "inputs": [], 62 | "name": "owner", 63 | "outputs": [ 64 | { 65 | "internalType": "address", 66 | "name": "", 67 | "type": "address" 68 | } 69 | ], 70 | "stateMutability": "view", 71 | "type": "function" 72 | }, 73 | { 74 | "inputs": [], 75 | "name": "pendingOwner", 76 | "outputs": [ 77 | { 78 | "internalType": "address", 79 | "name": "", 80 | "type": "address" 81 | } 82 | ], 83 | "stateMutability": "view", 84 | "type": "function" 85 | }, 86 | { 87 | "inputs": [ 88 | { 89 | "internalType": "contract IERC20", 90 | "name": "tokenContract", 91 | "type": "address" 92 | }, 93 | { 94 | "internalType": "address", 95 | "name": "to", 96 | "type": "address" 97 | }, 98 | { 99 | "internalType": "uint256", 100 | "name": "amount", 101 | "type": "uint256" 102 | } 103 | ], 104 | "name": "rescueERC20", 105 | "outputs": [], 106 | "stateMutability": "nonpayable", 107 | "type": "function" 108 | }, 109 | { 110 | "inputs": [], 111 | "name": "rescuer", 112 | "outputs": [ 113 | { 114 | "internalType": "address", 115 | "name": "", 116 | "type": "address" 117 | } 118 | ], 119 | "stateMutability": "view", 120 | "type": "function" 121 | }, 122 | { 123 | "inputs": [ 124 | { 125 | "internalType": "address", 126 | "name": "newOwner", 127 | "type": "address" 128 | } 129 | ], 130 | "name": "transferOwnership", 131 | "outputs": [], 132 | "stateMutability": "nonpayable", 133 | "type": "function" 134 | }, 135 | { 136 | "inputs": [ 137 | { 138 | "internalType": "address", 139 | "name": "newRescuer", 140 | "type": "address" 141 | } 142 | ], 143 | "name": "updateRescuer", 144 | "outputs": [], 145 | "stateMutability": "nonpayable", 146 | "type": "function" 147 | } 148 | ] -------------------------------------------------------------------------------- /docs/abis/cctp/Pausable.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "anonymous": false, 4 | "inputs": [ 5 | { 6 | "indexed": true, 7 | "internalType": "address", 8 | "name": "previousOwner", 9 | "type": "address" 10 | }, 11 | { 12 | "indexed": true, 13 | "internalType": "address", 14 | "name": "newOwner", 15 | "type": "address" 16 | } 17 | ], 18 | "name": "OwnershipTransferStarted", 19 | "type": "event" 20 | }, 21 | { 22 | "anonymous": false, 23 | "inputs": [ 24 | { 25 | "indexed": true, 26 | "internalType": "address", 27 | "name": "previousOwner", 28 | "type": "address" 29 | }, 30 | { 31 | "indexed": true, 32 | "internalType": "address", 33 | "name": "newOwner", 34 | "type": "address" 35 | } 36 | ], 37 | "name": "OwnershipTransferred", 38 | "type": "event" 39 | }, 40 | { 41 | "anonymous": false, 42 | "inputs": [], 43 | "name": "Pause", 44 | "type": "event" 45 | }, 46 | { 47 | "anonymous": false, 48 | "inputs": [ 49 | { 50 | "indexed": true, 51 | "internalType": "address", 52 | "name": "newAddress", 53 | "type": "address" 54 | } 55 | ], 56 | "name": "PauserChanged", 57 | "type": "event" 58 | }, 59 | { 60 | "anonymous": false, 61 | "inputs": [], 62 | "name": "Unpause", 63 | "type": "event" 64 | }, 65 | { 66 | "inputs": [], 67 | "name": "acceptOwnership", 68 | "outputs": [], 69 | "stateMutability": "nonpayable", 70 | "type": "function" 71 | }, 72 | { 73 | "inputs": [], 74 | "name": "owner", 75 | "outputs": [ 76 | { 77 | "internalType": "address", 78 | "name": "", 79 | "type": "address" 80 | } 81 | ], 82 | "stateMutability": "view", 83 | "type": "function" 84 | }, 85 | { 86 | "inputs": [], 87 | "name": "pause", 88 | "outputs": [], 89 | "stateMutability": "nonpayable", 90 | "type": "function" 91 | }, 92 | { 93 | "inputs": [], 94 | "name": "paused", 95 | "outputs": [ 96 | { 97 | "internalType": "bool", 98 | "name": "", 99 | "type": "bool" 100 | } 101 | ], 102 | "stateMutability": "view", 103 | "type": "function" 104 | }, 105 | { 106 | "inputs": [], 107 | "name": "pauser", 108 | "outputs": [ 109 | { 110 | "internalType": "address", 111 | "name": "", 112 | "type": "address" 113 | } 114 | ], 115 | "stateMutability": "view", 116 | "type": "function" 117 | }, 118 | { 119 | "inputs": [], 120 | "name": "pendingOwner", 121 | "outputs": [ 122 | { 123 | "internalType": "address", 124 | "name": "", 125 | "type": "address" 126 | } 127 | ], 128 | "stateMutability": "view", 129 | "type": "function" 130 | }, 131 | { 132 | "inputs": [ 133 | { 134 | "internalType": "address", 135 | "name": "newOwner", 136 | "type": "address" 137 | } 138 | ], 139 | "name": "transferOwnership", 140 | "outputs": [], 141 | "stateMutability": "nonpayable", 142 | "type": "function" 143 | }, 144 | { 145 | "inputs": [], 146 | "name": "unpause", 147 | "outputs": [], 148 | "stateMutability": "nonpayable", 149 | "type": "function" 150 | }, 151 | { 152 | "inputs": [ 153 | { 154 | "internalType": "address", 155 | "name": "_newPauser", 156 | "type": "address" 157 | } 158 | ], 159 | "name": "updatePauser", 160 | "outputs": [], 161 | "stateMutability": "nonpayable", 162 | "type": "function" 163 | } 164 | ] -------------------------------------------------------------------------------- /docs/abis/cctp/proxy/AdminUpgradableProxy.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "constructor", 4 | "inputs": [ 5 | { 6 | "name": "_logic", 7 | "type": "address", 8 | "internalType": "address" 9 | }, 10 | { 11 | "name": "admin_", 12 | "type": "address", 13 | "internalType": "address" 14 | }, 15 | { 16 | "name": "_data", 17 | "type": "bytes", 18 | "internalType": "bytes" 19 | } 20 | ], 21 | "stateMutability": "payable" 22 | }, 23 | { 24 | "type": "fallback", 25 | "stateMutability": "payable" 26 | }, 27 | { 28 | "type": "receive", 29 | "stateMutability": "payable" 30 | }, 31 | { 32 | "type": "function", 33 | "name": "admin", 34 | "inputs": [], 35 | "outputs": [ 36 | { 37 | "name": "admin_", 38 | "type": "address", 39 | "internalType": "address" 40 | } 41 | ], 42 | "stateMutability": "view" 43 | }, 44 | { 45 | "type": "function", 46 | "name": "changeAdmin", 47 | "inputs": [ 48 | { 49 | "name": "newAdmin", 50 | "type": "address", 51 | "internalType": "address" 52 | } 53 | ], 54 | "outputs": [], 55 | "stateMutability": "nonpayable" 56 | }, 57 | { 58 | "type": "function", 59 | "name": "implementation", 60 | "inputs": [], 61 | "outputs": [ 62 | { 63 | "name": "implementation_", 64 | "type": "address", 65 | "internalType": "address" 66 | } 67 | ], 68 | "stateMutability": "view" 69 | }, 70 | { 71 | "type": "function", 72 | "name": "upgradeTo", 73 | "inputs": [ 74 | { 75 | "name": "newImplementation", 76 | "type": "address", 77 | "internalType": "address" 78 | } 79 | ], 80 | "outputs": [], 81 | "stateMutability": "nonpayable" 82 | }, 83 | { 84 | "type": "function", 85 | "name": "upgradeToAndCall", 86 | "inputs": [ 87 | { 88 | "name": "newImplementation", 89 | "type": "address", 90 | "internalType": "address" 91 | }, 92 | { 93 | "name": "data", 94 | "type": "bytes", 95 | "internalType": "bytes" 96 | } 97 | ], 98 | "outputs": [], 99 | "stateMutability": "payable" 100 | }, 101 | { 102 | "type": "event", 103 | "name": "AdminChanged", 104 | "inputs": [ 105 | { 106 | "name": "previousAdmin", 107 | "type": "address", 108 | "indexed": false, 109 | "internalType": "address" 110 | }, 111 | { 112 | "name": "newAdmin", 113 | "type": "address", 114 | "indexed": false, 115 | "internalType": "address" 116 | } 117 | ], 118 | "anonymous": false 119 | }, 120 | { 121 | "type": "event", 122 | "name": "Upgraded", 123 | "inputs": [ 124 | { 125 | "name": "implementation", 126 | "type": "address", 127 | "indexed": true, 128 | "internalType": "address" 129 | } 130 | ], 131 | "anonymous": false 132 | } 133 | ] -------------------------------------------------------------------------------- /src/v2/BaseMessageTransmitter.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Circle Internet Group, Inc. All rights reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | pragma solidity 0.7.6; 19 | 20 | import {AttestableV2} from "../roles/v2/AttestableV2.sol"; 21 | import {Pausable} from "../roles/Pausable.sol"; 22 | import {Rescuable} from "../roles/Rescuable.sol"; 23 | import {Initializable} from "../proxy/Initializable.sol"; 24 | 25 | /** 26 | * @title BaseMessageTransmitter 27 | * @notice A base type containing administrative and configuration functionality for message transmitters. 28 | */ 29 | contract BaseMessageTransmitter is 30 | Initializable, 31 | Pausable, 32 | Rescuable, 33 | AttestableV2 34 | { 35 | // ============ Events ============ 36 | /** 37 | * @notice Emitted when max message body size is updated 38 | * @param newMaxMessageBodySize new maximum message body size, in bytes 39 | */ 40 | event MaxMessageBodySizeUpdated(uint256 newMaxMessageBodySize); 41 | 42 | // ============ Constants ============ 43 | // A constant value indicating that a nonce has been used 44 | uint256 public constant NONCE_USED = 1; 45 | 46 | // ============ State Variables ============ 47 | // Domain of chain on which the contract is deployed 48 | uint32 public immutable localDomain; 49 | 50 | // Message Format version 51 | uint32 public immutable version; 52 | 53 | // Maximum size of message body, in bytes. 54 | // This value is set by owner. 55 | uint256 public maxMessageBodySize; 56 | 57 | // Maps a bytes32 nonce -> uint256 (0 if unused, 1 if used) 58 | mapping(bytes32 => uint256) public usedNonces; 59 | 60 | // ============ Constructor ============ 61 | /** 62 | * @param _localDomain Domain of chain on which the contract is deployed 63 | * @param _version Message Format version 64 | */ 65 | constructor(uint32 _localDomain, uint32 _version) AttestableV2() { 66 | localDomain = _localDomain; 67 | version = _version; 68 | } 69 | 70 | // ============ External Functions ============ 71 | /** 72 | * @notice Sets the max message body size 73 | * @dev This value should not be reduced without good reason, 74 | * to avoid impacting users who rely on large messages. 75 | * @param newMaxMessageBodySize new max message body size, in bytes 76 | */ 77 | function setMaxMessageBodySize( 78 | uint256 newMaxMessageBodySize 79 | ) external onlyOwner { 80 | _setMaxMessageBodySize(newMaxMessageBodySize); 81 | } 82 | 83 | /** 84 | * @notice Returns the current initialized version 85 | */ 86 | function initializedVersion() external view returns (uint64) { 87 | return _getInitializedVersion(); 88 | } 89 | 90 | // ============ Internal Utils ============ 91 | /** 92 | * @notice Sets the max message body size 93 | * @param _newMaxMessageBodySize new max message body size, in bytes 94 | */ 95 | function _setMaxMessageBodySize(uint256 _newMaxMessageBodySize) internal { 96 | maxMessageBodySize = _newMaxMessageBodySize; 97 | emit MaxMessageBodySizeUpdated(maxMessageBodySize); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /docs/abis/cctp/v2/Create2Factory.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "function", 4 | "name": "computeAddress", 5 | "inputs": [ 6 | { 7 | "name": "salt", 8 | "type": "bytes32", 9 | "internalType": "bytes32" 10 | }, 11 | { 12 | "name": "bytecodeHash", 13 | "type": "bytes32", 14 | "internalType": "bytes32" 15 | } 16 | ], 17 | "outputs": [ 18 | { 19 | "name": "addr", 20 | "type": "address", 21 | "internalType": "address" 22 | } 23 | ], 24 | "stateMutability": "view" 25 | }, 26 | { 27 | "type": "function", 28 | "name": "deploy", 29 | "inputs": [ 30 | { 31 | "name": "amount", 32 | "type": "uint256", 33 | "internalType": "uint256" 34 | }, 35 | { 36 | "name": "salt", 37 | "type": "bytes32", 38 | "internalType": "bytes32" 39 | }, 40 | { 41 | "name": "bytecode", 42 | "type": "bytes", 43 | "internalType": "bytes" 44 | } 45 | ], 46 | "outputs": [ 47 | { 48 | "name": "addr", 49 | "type": "address", 50 | "internalType": "address" 51 | } 52 | ], 53 | "stateMutability": "payable" 54 | }, 55 | { 56 | "type": "function", 57 | "name": "deployAndMultiCall", 58 | "inputs": [ 59 | { 60 | "name": "amount", 61 | "type": "uint256", 62 | "internalType": "uint256" 63 | }, 64 | { 65 | "name": "salt", 66 | "type": "bytes32", 67 | "internalType": "bytes32" 68 | }, 69 | { 70 | "name": "bytecode", 71 | "type": "bytes", 72 | "internalType": "bytes" 73 | }, 74 | { 75 | "name": "data", 76 | "type": "bytes[]", 77 | "internalType": "bytes[]" 78 | } 79 | ], 80 | "outputs": [ 81 | { 82 | "name": "addr", 83 | "type": "address", 84 | "internalType": "address" 85 | } 86 | ], 87 | "stateMutability": "payable" 88 | }, 89 | { 90 | "type": "function", 91 | "name": "owner", 92 | "inputs": [], 93 | "outputs": [ 94 | { 95 | "name": "", 96 | "type": "address", 97 | "internalType": "address" 98 | } 99 | ], 100 | "stateMutability": "view" 101 | }, 102 | { 103 | "type": "function", 104 | "name": "transferOwnership", 105 | "inputs": [ 106 | { 107 | "name": "newOwner", 108 | "type": "address", 109 | "internalType": "address" 110 | } 111 | ], 112 | "outputs": [], 113 | "stateMutability": "nonpayable" 114 | }, 115 | { 116 | "type": "event", 117 | "name": "OwnershipTransferred", 118 | "inputs": [ 119 | { 120 | "name": "previousOwner", 121 | "type": "address", 122 | "indexed": true, 123 | "internalType": "address" 124 | }, 125 | { 126 | "name": "newOwner", 127 | "type": "address", 128 | "indexed": true, 129 | "internalType": "address" 130 | } 131 | ], 132 | "anonymous": false 133 | } 134 | ] -------------------------------------------------------------------------------- /test/messages/v2/MessageV2.t.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Circle Internet Group, Inc. All rights reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | pragma solidity 0.7.6; 19 | pragma abicoder v2; 20 | 21 | import {Test} from "forge-std/Test.sol"; 22 | import {MessageV2} from "../../../src/messages/v2/MessageV2.sol"; 23 | import {TypedMemView} from "@memview-sol/contracts/TypedMemView.sol"; 24 | 25 | contract MessageV2Test is Test { 26 | using TypedMemView for bytes; 27 | using TypedMemView for bytes29; 28 | using MessageV2 for bytes29; 29 | 30 | function testFormatMessage( 31 | uint32 _version, 32 | uint32 _sourceDomain, 33 | uint32 _destinationDomain, 34 | bytes32 _sender, 35 | bytes32 _recipient, 36 | bytes32 _destinationCaller, 37 | uint32 _minFinalityThreshold, 38 | bytes calldata _messageBody 39 | ) public view { 40 | bytes memory _message = MessageV2._formatMessageForRelay( 41 | _version, 42 | _sourceDomain, 43 | _destinationDomain, 44 | _sender, 45 | _recipient, 46 | _destinationCaller, 47 | _minFinalityThreshold, 48 | _messageBody 49 | ); 50 | 51 | bytes29 _m = _message.ref(0); 52 | assertEq(uint256(_m._getVersion()), uint256(_version)); 53 | assertEq(uint256(_m._getSourceDomain()), uint256(_sourceDomain)); 54 | assertEq( 55 | uint256(_m._getDestinationDomain()), 56 | uint256(_destinationDomain) 57 | ); 58 | 59 | assertEq(_m._getNonce(), bytes32(0)); 60 | assertEq(_m._getSender(), _sender); 61 | assertEq(_m._getRecipient(), _recipient); 62 | assertEq(_m._getDestinationCaller(), _destinationCaller); 63 | assertEq( 64 | uint256(_m._getMinFinalityThreshold()), 65 | uint256(_minFinalityThreshold) 66 | ); 67 | assertEq(uint256(_m._getFinalityThresholdExecuted()), uint256(0)); 68 | assertEq(_m._getMessageBody().clone(), _messageBody); 69 | } 70 | 71 | function testIsValidMessage_revertsForEmptyMessage() public { 72 | bytes29 _m = TypedMemView.nullView(); 73 | vm.expectRevert("Malformed message"); 74 | MessageV2._validateMessageFormat(_m); 75 | } 76 | 77 | function testIsValidMessage_revertsForTooShortMessage( 78 | uint32 _version, 79 | uint32 _sourceDomain, 80 | uint32 _destinationDomain, 81 | bytes32 _sender, 82 | bytes32 _recipient, 83 | bytes32 _destinationCaller, 84 | uint32 _minFinalityThreshold, 85 | bytes calldata _messageBody 86 | ) public { 87 | bytes memory _message = MessageV2._formatMessageForRelay( 88 | _version, 89 | _sourceDomain, 90 | _destinationDomain, 91 | _sender, 92 | _recipient, 93 | _destinationCaller, 94 | _minFinalityThreshold, 95 | _messageBody 96 | ); 97 | bytes29 _m = _message.ref(0); 98 | 99 | // Lop off the _messageBody bytes, and then one more 100 | _m = _m.slice(0, _m.len() - _messageBody.length - 1, 0); 101 | 102 | vm.expectRevert("Invalid message: too short"); 103 | MessageV2._validateMessageFormat(_m); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /test/messages/v2/BurnMessageV2.t.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Circle Internet Group, Inc. All rights reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | pragma solidity 0.7.6; 19 | pragma abicoder v2; 20 | 21 | import {TypedMemView} from "@memview-sol/contracts/TypedMemView.sol"; 22 | import {Test} from "forge-std/Test.sol"; 23 | import {BurnMessageV2} from "../../../src/messages/v2/BurnMessageV2.sol"; 24 | 25 | contract BurnMessageV2Test is Test { 26 | using TypedMemView for bytes; 27 | using TypedMemView for bytes29; 28 | using BurnMessageV2 for bytes29; 29 | 30 | function testFormatMessageyForRelay_succeeds( 31 | uint32 _version, 32 | bytes32 _burnToken, 33 | bytes32 _mintRecipient, 34 | uint256 _amount, 35 | bytes32 _messageSender, 36 | uint256 _maxFee, 37 | bytes calldata _hookData 38 | ) public view { 39 | bytes memory _expectedMessageBody = abi.encodePacked( 40 | _version, 41 | _burnToken, 42 | _mintRecipient, 43 | _amount, 44 | _messageSender, 45 | _maxFee, 46 | uint256(0), 47 | uint256(0), 48 | _hookData 49 | ); 50 | 51 | bytes memory _messageBody = BurnMessageV2._formatMessageForRelay( 52 | _version, 53 | _burnToken, 54 | _mintRecipient, 55 | _amount, 56 | _messageSender, 57 | _maxFee, 58 | _hookData 59 | ); 60 | 61 | bytes29 _m = _messageBody.ref(0); 62 | assertEq(uint256(_m._getVersion()), uint256(_version)); 63 | assertEq(_m._getBurnToken(), _burnToken); 64 | assertEq(_m._getMintRecipient(), _mintRecipient); 65 | assertEq(_m._getAmount(), _amount); 66 | assertEq(_m._getMessageSender(), _messageSender); 67 | assertEq(_m._getMaxFee(), _maxFee); 68 | assertEq(_m._getFeeExecuted(), 0); 69 | assertEq(_m._getExpirationBlock(), 0); 70 | assertEq(_m._getHookData().clone(), _hookData); 71 | 72 | _m._validateBurnMessageFormat(); 73 | 74 | assertEq(_expectedMessageBody.ref(0).keccak(), _m.keccak()); 75 | } 76 | 77 | function testIsValidBurnMessage_revertsForTooShortMessage( 78 | uint32 _version, 79 | bytes32 _burnToken, 80 | bytes32 _mintRecipient, 81 | uint256 _amount, 82 | bytes32 _messageSender, 83 | uint256 _maxFee, 84 | bytes calldata _hookData 85 | ) public { 86 | bytes memory _messageBody = BurnMessageV2._formatMessageForRelay( 87 | _version, 88 | _burnToken, 89 | _mintRecipient, 90 | _amount, 91 | _messageSender, 92 | _maxFee, 93 | _hookData 94 | ); 95 | bytes29 _m = _messageBody.ref(0); 96 | 97 | // Lop off the hookData bytes, and then one more 98 | _m = _m.slice(0, _m.len() - _hookData.length - 1, 0); 99 | 100 | vm.expectRevert("Invalid burn message: too short"); 101 | _m._validateBurnMessageFormat(); 102 | } 103 | 104 | function testIsValidBurnMessage_revertsForEmptyMessage() public { 105 | bytes29 _m = TypedMemView.nullView(); 106 | vm.expectRevert("Malformed message"); 107 | BurnMessageV2._validateBurnMessageFormat(_m); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /test/messages/BurnMessage.t.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, Circle Internet Financial Limited. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | pragma solidity 0.7.6; 17 | pragma abicoder v2; 18 | 19 | import "@memview-sol/contracts/TypedMemView.sol"; 20 | import "forge-std/Test.sol"; 21 | import "../../src/messages/BurnMessage.sol"; 22 | import "../../src/messages/Message.sol"; 23 | 24 | contract BurnMessageTest is Test { 25 | using TypedMemView for bytes; 26 | using TypedMemView for bytes29; 27 | using BurnMessage for bytes29; 28 | 29 | uint32 version = 1; 30 | 31 | function testFormatMessage_succeeds( 32 | bytes32 _burnToken, 33 | bytes32 _mintRecipient, 34 | uint256 _amount, 35 | address msgSender 36 | ) public { 37 | bytes memory _expectedMessageBody = abi.encodePacked( 38 | version, 39 | _burnToken, 40 | _mintRecipient, 41 | _amount, 42 | Message.addressToBytes32(msgSender) 43 | ); 44 | 45 | bytes memory _messageBody = BurnMessage._formatMessage( 46 | version, 47 | _burnToken, 48 | _mintRecipient, 49 | _amount, 50 | Message.addressToBytes32(msgSender) 51 | ); 52 | 53 | bytes29 _m = _messageBody.ref(0); 54 | assertEq(_m._getMintRecipient(), _mintRecipient); 55 | assertEq(_m._getBurnToken(), _burnToken); 56 | assertEq(_m._getAmount(), _amount); 57 | assertEq(uint256(_m._getVersion()), uint256(version)); 58 | assertEq(_m._getMessageSender(), Message.addressToBytes32(msgSender)); 59 | _m._validateBurnMessageFormat(); 60 | 61 | assertEq(_expectedMessageBody.ref(0).keccak(), _m.keccak()); 62 | } 63 | 64 | function testIsValidBurnMessage_returnsFalseForTooShortMessage( 65 | bytes32 _burnToken, 66 | bytes32 _mintRecipient, 67 | uint256 _amount 68 | ) public { 69 | bytes memory _messageBody = BurnMessage._formatMessage( 70 | version, 71 | _burnToken, 72 | _mintRecipient, 73 | _amount, 74 | Message.addressToBytes32(msg.sender) 75 | ); 76 | 77 | bytes29 _m = _messageBody.ref(0); 78 | _m = _m.slice(0, _m.len() - 1, 0); 79 | 80 | vm.expectRevert("Invalid message length"); 81 | _m._validateBurnMessageFormat(); 82 | } 83 | 84 | function testIsValidBurnMessage_revertsForTooLongMessage( 85 | bytes32 _burnToken, 86 | bytes32 _mintRecipient, 87 | uint256 _amount, 88 | address msgSender 89 | ) public { 90 | bytes memory _tooLongMessageBody = abi.encodePacked( 91 | version, 92 | _burnToken, 93 | _mintRecipient, 94 | _amount, 95 | Message.addressToBytes32(msgSender), 96 | _amount // encode _amount twice (invalid) 97 | ); 98 | 99 | bytes29 _m = _tooLongMessageBody.ref(0); 100 | assertEq(_m._getMintRecipient(), _mintRecipient); 101 | assertEq(_m._getBurnToken(), _burnToken); 102 | assertEq(_m._getAmount(), _amount); 103 | vm.expectRevert("Invalid message length"); 104 | _m._validateBurnMessageFormat(); 105 | } 106 | 107 | function testIsValidBurnMessage_revertsForMalformedMessage() public { 108 | bytes29 _m = TypedMemView.nullView(); 109 | vm.expectRevert("Malformed message"); 110 | BurnMessage._validateBurnMessageFormat(_m); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /test/messages/Message.t.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, Circle Internet Financial Limited. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | pragma solidity 0.7.6; 17 | pragma abicoder v2; 18 | 19 | import "forge-std/Test.sol"; 20 | import "../../src/messages/Message.sol"; 21 | 22 | contract MessageTest is Test { 23 | using TypedMemView for bytes; 24 | using TypedMemView for bytes29; 25 | using Message for bytes29; 26 | 27 | function testFormatMessage_fuzz( 28 | uint32 _version, 29 | uint32 _sourceDomain, 30 | uint32 _destinationDomain, 31 | uint64 _nonce, 32 | bytes32 _sender, 33 | bytes32 _recipient, 34 | bytes32 _destinationCaller, 35 | bytes memory _messageBody 36 | ) public { 37 | bytes memory message = Message._formatMessage( 38 | _version, 39 | _sourceDomain, 40 | _destinationDomain, 41 | _nonce, 42 | _sender, 43 | _recipient, 44 | _destinationCaller, 45 | _messageBody 46 | ); 47 | 48 | bytes29 _m = message.ref(0); 49 | assertEq(uint256(_m._version()), uint256(_version)); 50 | assertEq(uint256(_m._sourceDomain()), uint256(_sourceDomain)); 51 | assertEq(uint256(_m._destinationDomain()), uint256(_destinationDomain)); 52 | assertEq(_m._nonce(), uint256(_nonce)); 53 | assertEq(_m._sender(), _sender); 54 | assertEq(_m._recipient(), _recipient); 55 | assertEq(_m._destinationCaller(), _destinationCaller); 56 | assertEq(_m._messageBody().clone(), _messageBody); 57 | } 58 | 59 | function testFormatMessage() public { 60 | uint32 _version = 1; 61 | uint32 _sourceDomain = 1111; 62 | uint32 _destinationDomain = 1234; 63 | uint32 _nonce = 4294967295; // uint32 max value 64 | 65 | bytes32 _sender = bytes32(uint256(uint160(vm.addr(1505)))); 66 | bytes32 _recipient = bytes32(uint256(uint160(vm.addr(1506)))); 67 | bytes32 _destinationCaller = bytes32(uint256(uint160(vm.addr(1507)))); 68 | bytes memory _messageBody = bytes("test message"); 69 | 70 | bytes memory message = Message._formatMessage( 71 | _version, 72 | _sourceDomain, 73 | _destinationDomain, 74 | _nonce, 75 | _sender, 76 | _recipient, 77 | _destinationCaller, 78 | _messageBody 79 | ); 80 | 81 | bytes29 _m = message.ref(0); 82 | assertEq(uint256(_m._version()), uint256(_version)); 83 | assertEq(uint256(_m._sourceDomain()), uint256(_sourceDomain)); 84 | assertEq(uint256(_m._destinationDomain()), uint256(_destinationDomain)); 85 | assertEq(_m._sender(), _sender); 86 | assertEq(_m._recipient(), _recipient); 87 | assertEq(_m._messageBody().clone(), _messageBody); 88 | assertEq(uint256(_m._nonce()), uint256(_nonce)); 89 | } 90 | 91 | function testAddressToBytes32ToAddress_fuzz(address _addr) public { 92 | bytes32 _bytes32FromAddr = Message.addressToBytes32(_addr); 93 | address _addrFromBytes32 = Message.bytes32ToAddress(_bytes32FromAddr); 94 | assertEq(_addrFromBytes32, _addr); 95 | } 96 | 97 | function testIsValidMessage_revertsForMalformedMessage() public { 98 | bytes29 _m = TypedMemView.nullView(); 99 | vm.expectRevert("Malformed message"); 100 | Message._validateMessageFormat(_m); 101 | } 102 | 103 | function testIsValidMessage_revertsForTooShortMessage() public { 104 | bytes memory _message = "foo"; 105 | bytes29 _m = _message.ref(0); 106 | 107 | vm.expectRevert("Invalid message: too short"); 108 | Message._validateMessageFormat(_m); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /test/v2/Create2Factory.t.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Circle Internet Group, Inc. All rights reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | pragma solidity 0.7.6; 19 | pragma abicoder v2; 20 | 21 | import {Create2Factory} from "../../src/v2/Create2Factory.sol"; 22 | import {MockInitializableImplementation} from "../mocks/MockInitializableImplementation.sol"; 23 | import {UpgradeableProxy} from "@openzeppelin/contracts/proxy/UpgradeableProxy.sol"; 24 | import {Test} from "forge-std/Test.sol"; 25 | 26 | contract Create2FactoryTest is Test { 27 | Create2Factory private create2Factory; 28 | MockInitializableImplementation private impl; 29 | 30 | event Upgraded(address indexed implementation); 31 | 32 | function setUp() public { 33 | create2Factory = new Create2Factory(); 34 | impl = new MockInitializableImplementation(); 35 | } 36 | 37 | function test_SetUpState() public { 38 | // Check owners 39 | assertEq(create2Factory.owner(), address(this)); 40 | } 41 | 42 | function testDeploy(address addr, uint256 num, bytes32 salt) public { 43 | // Construct initializer 44 | bytes memory initializer = abi.encodeWithSelector( 45 | MockInitializableImplementation.initialize.selector, 46 | addr, 47 | num 48 | ); 49 | // Construct bytecode 50 | bytes memory bytecode = abi.encodePacked( 51 | type(UpgradeableProxy).creationCode, 52 | abi.encode(address(impl), initializer) 53 | ); 54 | // Deploy proxy 55 | address expectedAddr = create2Factory.computeAddress( 56 | salt, 57 | keccak256(bytecode) 58 | ); 59 | address proxyAddr = create2Factory.deploy(0, salt, bytecode); 60 | 61 | // Verify deterministic 62 | assertEq(proxyAddr, expectedAddr); 63 | // Check initialized vars 64 | assertEq(MockInitializableImplementation(proxyAddr).addr(), addr); 65 | assertEq(MockInitializableImplementation(proxyAddr).num(), num); 66 | } 67 | 68 | function testDeployAndMultiCall( 69 | address addr, 70 | uint256 num, 71 | uint256 amount, 72 | bytes32 salt 73 | ) public { 74 | // Construct initializers 75 | bytes memory initializer1 = abi.encodeWithSelector( 76 | MockInitializableImplementation.initialize.selector, 77 | addr, 78 | num 79 | ); 80 | bytes memory initializer2 = abi.encodeWithSelector( 81 | MockInitializableImplementation.initializeV2.selector 82 | ); 83 | bytes[] memory data = new bytes[](2); 84 | data[0] = initializer1; 85 | data[1] = initializer2; 86 | // Construct bytecode 87 | bytes memory bytecode = abi.encodePacked( 88 | type(UpgradeableProxy).creationCode, 89 | abi.encode(address(impl), "") 90 | ); 91 | // Deploy proxy 92 | address expectedAddr = create2Factory.computeAddress( 93 | salt, 94 | keccak256(bytecode) 95 | ); 96 | vm.deal(address(this), amount); 97 | 98 | // Expect calls 99 | vm.expectCall(expectedAddr, initializer1); 100 | vm.expectCall(expectedAddr, initializer2); 101 | 102 | address proxyAddr = create2Factory.deployAndMultiCall{value: amount}( 103 | amount, 104 | salt, 105 | bytecode, 106 | data 107 | ); 108 | 109 | // Verify deterministic 110 | assertEq(proxyAddr, expectedAddr); 111 | // Check initialized vars 112 | assertEq(MockInitializableImplementation(proxyAddr).addr(), addr); 113 | assertEq(MockInitializableImplementation(proxyAddr).num(), num); 114 | // Verify balance 115 | assertEq(proxyAddr.balance, amount); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /scripts/v2/SetupRemoteResourcesV2.s.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Circle Internet Group, Inc. All rights reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | pragma solidity 0.7.6; 19 | 20 | import {Script} from "forge-std/Script.sol"; 21 | import {TokenMessengerV2} from "../../src/v2/TokenMessengerV2.sol"; 22 | import {TokenMinterV2} from "../../src/v2/TokenMinterV2.sol"; 23 | import {Message} from "../../src/messages/Message.sol"; 24 | 25 | contract SetupRemoteResourcesV2Script is Script { 26 | address private usdcRemoteContractAddress; 27 | address private usdcContractAddress; 28 | address private tokenMessengerV2ContractAddress; 29 | address private tokenMinterV2ContractAddress; 30 | 31 | uint32 private remoteDomain; 32 | 33 | uint256 private tokenMessengerV2OwnerPrivateKey; 34 | uint256 private tokenControllerPrivateKey; 35 | 36 | /** 37 | * @notice link current chain and remote chain tokens 38 | */ 39 | function linkTokenPairV2( 40 | TokenMinterV2 tokenMinterV2, 41 | uint256 privateKey 42 | ) private { 43 | // Start recording transactions 44 | vm.startBroadcast(privateKey); 45 | 46 | bytes32 remoteUsdcContractAddressInBytes32 = Message.addressToBytes32( 47 | usdcRemoteContractAddress 48 | ); 49 | 50 | tokenMinterV2.linkTokenPair( 51 | usdcContractAddress, 52 | remoteDomain, 53 | remoteUsdcContractAddressInBytes32 54 | ); 55 | 56 | // Stop recording transactions 57 | vm.stopBroadcast(); 58 | } 59 | 60 | /** 61 | * @notice add address of TokenMessenger deployed on another chain 62 | */ 63 | function addRemoteTokenMessengerV2( 64 | TokenMessengerV2 tokenMessengerV2, 65 | uint256 privateKey 66 | ) private { 67 | // Start recording transactions 68 | vm.startBroadcast(privateKey); 69 | bytes32 remoteTokenMessengerAddressInBytes32 = Message.addressToBytes32( 70 | address(tokenMessengerV2) 71 | ); 72 | tokenMessengerV2.addRemoteTokenMessenger( 73 | remoteDomain, 74 | remoteTokenMessengerAddressInBytes32 75 | ); 76 | 77 | // Stop recording transactions 78 | vm.stopBroadcast(); 79 | } 80 | 81 | /** 82 | * @notice initialize variables from environment 83 | */ 84 | function setUp() public { 85 | tokenMessengerV2OwnerPrivateKey = vm.envUint( 86 | "TOKEN_MESSENGER_V2_OWNER_KEY" 87 | ); 88 | tokenControllerPrivateKey = vm.envUint("TOKEN_CONTROLLER_KEY"); 89 | 90 | tokenMessengerV2ContractAddress = vm.envAddress( 91 | "TOKEN_MESSENGER_V2_CONTRACT_ADDRESS" 92 | ); 93 | tokenMinterV2ContractAddress = vm.envAddress( 94 | "TOKEN_MINTER_V2_CONTRACT_ADDRESS" 95 | ); 96 | usdcContractAddress = vm.envAddress("USDC_CONTRACT_ADDRESS"); 97 | usdcRemoteContractAddress = vm.envAddress( 98 | "REMOTE_USDC_CONTRACT_ADDRESS" 99 | ); 100 | 101 | remoteDomain = uint32(vm.envUint("REMOTE_DOMAIN")); 102 | } 103 | 104 | /** 105 | * @notice main function that will be run by forge 106 | * this links the remote usdc token and the remote token messenger 107 | */ 108 | function run() public { 109 | TokenMessengerV2 tokenMessengerV2 = TokenMessengerV2( 110 | tokenMessengerV2ContractAddress 111 | ); 112 | TokenMinterV2 tokenMinterV2 = TokenMinterV2( 113 | tokenMinterV2ContractAddress 114 | ); 115 | 116 | // Link token pair and add remote token messenger 117 | linkTokenPairV2(tokenMinterV2, tokenControllerPrivateKey); 118 | addRemoteTokenMessengerV2( 119 | tokenMessengerV2, 120 | tokenMessengerV2OwnerPrivateKey 121 | ); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /docs/abis/cctp/v2.1/Denylistable.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "function", 4 | "name": "acceptOwnership", 5 | "inputs": [], 6 | "outputs": [], 7 | "stateMutability": "nonpayable" 8 | }, 9 | { 10 | "type": "function", 11 | "name": "denylist", 12 | "inputs": [ 13 | { 14 | "name": "account", 15 | "type": "address", 16 | "internalType": "address" 17 | } 18 | ], 19 | "outputs": [], 20 | "stateMutability": "nonpayable" 21 | }, 22 | { 23 | "type": "function", 24 | "name": "denylister", 25 | "inputs": [], 26 | "outputs": [ 27 | { 28 | "name": "", 29 | "type": "address", 30 | "internalType": "address" 31 | } 32 | ], 33 | "stateMutability": "view" 34 | }, 35 | { 36 | "type": "function", 37 | "name": "isDenylisted", 38 | "inputs": [ 39 | { 40 | "name": "account", 41 | "type": "address", 42 | "internalType": "address" 43 | } 44 | ], 45 | "outputs": [ 46 | { 47 | "name": "", 48 | "type": "bool", 49 | "internalType": "bool" 50 | } 51 | ], 52 | "stateMutability": "view" 53 | }, 54 | { 55 | "type": "function", 56 | "name": "owner", 57 | "inputs": [], 58 | "outputs": [ 59 | { 60 | "name": "", 61 | "type": "address", 62 | "internalType": "address" 63 | } 64 | ], 65 | "stateMutability": "view" 66 | }, 67 | { 68 | "type": "function", 69 | "name": "pendingOwner", 70 | "inputs": [], 71 | "outputs": [ 72 | { 73 | "name": "", 74 | "type": "address", 75 | "internalType": "address" 76 | } 77 | ], 78 | "stateMutability": "view" 79 | }, 80 | { 81 | "type": "function", 82 | "name": "transferOwnership", 83 | "inputs": [ 84 | { 85 | "name": "newOwner", 86 | "type": "address", 87 | "internalType": "address" 88 | } 89 | ], 90 | "outputs": [], 91 | "stateMutability": "nonpayable" 92 | }, 93 | { 94 | "type": "function", 95 | "name": "unDenylist", 96 | "inputs": [ 97 | { 98 | "name": "account", 99 | "type": "address", 100 | "internalType": "address" 101 | } 102 | ], 103 | "outputs": [], 104 | "stateMutability": "nonpayable" 105 | }, 106 | { 107 | "type": "function", 108 | "name": "updateDenylister", 109 | "inputs": [ 110 | { 111 | "name": "newDenylister", 112 | "type": "address", 113 | "internalType": "address" 114 | } 115 | ], 116 | "outputs": [], 117 | "stateMutability": "nonpayable" 118 | }, 119 | { 120 | "type": "event", 121 | "name": "Denylisted", 122 | "inputs": [ 123 | { 124 | "name": "account", 125 | "type": "address", 126 | "indexed": true, 127 | "internalType": "address" 128 | } 129 | ], 130 | "anonymous": false 131 | }, 132 | { 133 | "type": "event", 134 | "name": "DenylisterChanged", 135 | "inputs": [ 136 | { 137 | "name": "oldDenylister", 138 | "type": "address", 139 | "indexed": true, 140 | "internalType": "address" 141 | }, 142 | { 143 | "name": "newDenylister", 144 | "type": "address", 145 | "indexed": true, 146 | "internalType": "address" 147 | } 148 | ], 149 | "anonymous": false 150 | }, 151 | { 152 | "type": "event", 153 | "name": "OwnershipTransferStarted", 154 | "inputs": [ 155 | { 156 | "name": "previousOwner", 157 | "type": "address", 158 | "indexed": true, 159 | "internalType": "address" 160 | }, 161 | { 162 | "name": "newOwner", 163 | "type": "address", 164 | "indexed": true, 165 | "internalType": "address" 166 | } 167 | ], 168 | "anonymous": false 169 | }, 170 | { 171 | "type": "event", 172 | "name": "OwnershipTransferred", 173 | "inputs": [ 174 | { 175 | "name": "previousOwner", 176 | "type": "address", 177 | "indexed": true, 178 | "internalType": "address" 179 | }, 180 | { 181 | "name": "newOwner", 182 | "type": "address", 183 | "indexed": true, 184 | "internalType": "address" 185 | } 186 | ], 187 | "anonymous": false 188 | }, 189 | { 190 | "type": "event", 191 | "name": "UnDenylisted", 192 | "inputs": [ 193 | { 194 | "name": "account", 195 | "type": "address", 196 | "indexed": true, 197 | "internalType": "address" 198 | } 199 | ], 200 | "anonymous": false 201 | } 202 | ] -------------------------------------------------------------------------------- /scripts/v2/PredictCreate2Deployments.s.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Circle Internet Group, Inc. All rights reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | pragma solidity 0.7.6; 19 | 20 | import {Script} from "forge-std/Script.sol"; 21 | import {MessageTransmitterV2} from "../../src/v2/MessageTransmitterV2.sol"; 22 | import {TokenMessengerV2} from "../../src/v2/TokenMessengerV2.sol"; 23 | import {TokenMinterV2} from "../../src/v2/TokenMinterV2.sol"; 24 | import {AdminUpgradableProxy} from "../../src/proxy/AdminUpgradableProxy.sol"; 25 | import {AddressUtilsExternal} from "../../src/messages/v2/AddressUtilsExternal.sol"; 26 | import {SALT_MESSAGE_TRANSMITTER, SALT_TOKEN_MESSENGER, SALT_TOKEN_MINTER, SALT_ADDRESS_UTILS_EXTERNAL} from "./Salts.sol"; 27 | 28 | contract PredictCreate2Deployments is Script { 29 | function messageTransmitterV2Proxy( 30 | address create2Factory 31 | ) public returns (address) { 32 | return 33 | vm.computeCreate2Address( 34 | SALT_MESSAGE_TRANSMITTER, 35 | keccak256( 36 | abi.encodePacked( 37 | type(AdminUpgradableProxy).creationCode, 38 | abi.encode(create2Factory, create2Factory, "") 39 | ) 40 | ), 41 | create2Factory 42 | ); 43 | } 44 | 45 | function messageTransmitterV2Impl( 46 | address create2Factory, 47 | uint32 domain, 48 | uint32 version 49 | ) public returns (address) { 50 | return 51 | vm.computeCreate2Address( 52 | SALT_MESSAGE_TRANSMITTER, 53 | keccak256( 54 | abi.encodePacked( 55 | type(MessageTransmitterV2).creationCode, 56 | abi.encode(domain, version) 57 | ) 58 | ), 59 | create2Factory 60 | ); 61 | } 62 | 63 | function tokenMessengerV2Proxy( 64 | address create2Factory 65 | ) public returns (address) { 66 | return 67 | vm.computeCreate2Address( 68 | SALT_TOKEN_MESSENGER, 69 | keccak256( 70 | abi.encodePacked( 71 | type(AdminUpgradableProxy).creationCode, 72 | abi.encode(create2Factory, create2Factory, "") 73 | ) 74 | ), 75 | create2Factory 76 | ); 77 | } 78 | 79 | function tokenMessengerV2Impl( 80 | address create2Factory, 81 | uint32 messageBodyVersion 82 | ) public returns (address) { 83 | address _messageTransmitterProxy = messageTransmitterV2Proxy( 84 | create2Factory 85 | ); 86 | return 87 | vm.computeCreate2Address( 88 | SALT_TOKEN_MESSENGER, 89 | keccak256( 90 | abi.encodePacked( 91 | type(TokenMessengerV2).creationCode, 92 | abi.encode(_messageTransmitterProxy, messageBodyVersion) 93 | ) 94 | ), 95 | create2Factory 96 | ); 97 | } 98 | 99 | function tokenMinterV2(address create2Factory) public returns (address) { 100 | return 101 | vm.computeCreate2Address( 102 | SALT_TOKEN_MINTER, 103 | keccak256( 104 | abi.encodePacked( 105 | type(TokenMinterV2).creationCode, 106 | abi.encode(create2Factory) 107 | ) 108 | ), 109 | create2Factory 110 | ); 111 | } 112 | 113 | function addressUtilsExternal( 114 | address create2Factory 115 | ) public returns (address) { 116 | return 117 | vm.computeCreate2Address( 118 | SALT_ADDRESS_UTILS_EXTERNAL, 119 | keccak256( 120 | abi.encodePacked(type(AddressUtilsExternal).creationCode) 121 | ), 122 | create2Factory 123 | ); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /docs/abis/cctp/TokenController.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "anonymous": false, 4 | "inputs": [ 5 | { 6 | "indexed": true, 7 | "internalType": "address", 8 | "name": "token", 9 | "type": "address" 10 | }, 11 | { 12 | "indexed": false, 13 | "internalType": "uint256", 14 | "name": "burnLimitPerMessage", 15 | "type": "uint256" 16 | } 17 | ], 18 | "name": "SetBurnLimitPerMessage", 19 | "type": "event" 20 | }, 21 | { 22 | "anonymous": false, 23 | "inputs": [ 24 | { 25 | "indexed": false, 26 | "internalType": "address", 27 | "name": "tokenController", 28 | "type": "address" 29 | } 30 | ], 31 | "name": "SetTokenController", 32 | "type": "event" 33 | }, 34 | { 35 | "anonymous": false, 36 | "inputs": [ 37 | { 38 | "indexed": false, 39 | "internalType": "address", 40 | "name": "localToken", 41 | "type": "address" 42 | }, 43 | { 44 | "indexed": false, 45 | "internalType": "uint32", 46 | "name": "remoteDomain", 47 | "type": "uint32" 48 | }, 49 | { 50 | "indexed": false, 51 | "internalType": "bytes32", 52 | "name": "remoteToken", 53 | "type": "bytes32" 54 | } 55 | ], 56 | "name": "TokenPairLinked", 57 | "type": "event" 58 | }, 59 | { 60 | "anonymous": false, 61 | "inputs": [ 62 | { 63 | "indexed": false, 64 | "internalType": "address", 65 | "name": "localToken", 66 | "type": "address" 67 | }, 68 | { 69 | "indexed": false, 70 | "internalType": "uint32", 71 | "name": "remoteDomain", 72 | "type": "uint32" 73 | }, 74 | { 75 | "indexed": false, 76 | "internalType": "bytes32", 77 | "name": "remoteToken", 78 | "type": "bytes32" 79 | } 80 | ], 81 | "name": "TokenPairUnlinked", 82 | "type": "event" 83 | }, 84 | { 85 | "inputs": [ 86 | { 87 | "internalType": "address", 88 | "name": "", 89 | "type": "address" 90 | } 91 | ], 92 | "name": "burnLimitsPerMessage", 93 | "outputs": [ 94 | { 95 | "internalType": "uint256", 96 | "name": "", 97 | "type": "uint256" 98 | } 99 | ], 100 | "stateMutability": "view", 101 | "type": "function" 102 | }, 103 | { 104 | "inputs": [ 105 | { 106 | "internalType": "address", 107 | "name": "localToken", 108 | "type": "address" 109 | }, 110 | { 111 | "internalType": "uint32", 112 | "name": "remoteDomain", 113 | "type": "uint32" 114 | }, 115 | { 116 | "internalType": "bytes32", 117 | "name": "remoteToken", 118 | "type": "bytes32" 119 | } 120 | ], 121 | "name": "linkTokenPair", 122 | "outputs": [], 123 | "stateMutability": "nonpayable", 124 | "type": "function" 125 | }, 126 | { 127 | "inputs": [ 128 | { 129 | "internalType": "bytes32", 130 | "name": "", 131 | "type": "bytes32" 132 | } 133 | ], 134 | "name": "remoteTokensToLocalTokens", 135 | "outputs": [ 136 | { 137 | "internalType": "address", 138 | "name": "", 139 | "type": "address" 140 | } 141 | ], 142 | "stateMutability": "view", 143 | "type": "function" 144 | }, 145 | { 146 | "inputs": [ 147 | { 148 | "internalType": "address", 149 | "name": "localToken", 150 | "type": "address" 151 | }, 152 | { 153 | "internalType": "uint256", 154 | "name": "burnLimitPerMessage", 155 | "type": "uint256" 156 | } 157 | ], 158 | "name": "setMaxBurnAmountPerMessage", 159 | "outputs": [], 160 | "stateMutability": "nonpayable", 161 | "type": "function" 162 | }, 163 | { 164 | "inputs": [], 165 | "name": "tokenController", 166 | "outputs": [ 167 | { 168 | "internalType": "address", 169 | "name": "", 170 | "type": "address" 171 | } 172 | ], 173 | "stateMutability": "view", 174 | "type": "function" 175 | }, 176 | { 177 | "inputs": [ 178 | { 179 | "internalType": "address", 180 | "name": "localToken", 181 | "type": "address" 182 | }, 183 | { 184 | "internalType": "uint32", 185 | "name": "remoteDomain", 186 | "type": "uint32" 187 | }, 188 | { 189 | "internalType": "bytes32", 190 | "name": "remoteToken", 191 | "type": "bytes32" 192 | } 193 | ], 194 | "name": "unlinkTokenPair", 195 | "outputs": [], 196 | "stateMutability": "nonpayable", 197 | "type": "function" 198 | } 199 | ] -------------------------------------------------------------------------------- /scripts/v2/RotateKeysV2.s.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Circle Internet Group, Inc. All rights reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | pragma solidity 0.7.6; 19 | 20 | import "forge-std/Script.sol"; 21 | import {TokenMessengerV2} from "../../src/v2/TokenMessengerV2.sol"; 22 | import {TokenMinterV2} from "../../src/v2/TokenMinterV2.sol"; 23 | import {MessageTransmitterV2} from "../../src/v2/MessageTransmitterV2.sol"; 24 | 25 | contract RotateKeysV2Script is Script { 26 | address private messageTransmitterV2ContractAddress; 27 | address private tokenMessengerV2ContractAddress; 28 | address private tokenMinterV2ContractAddress; 29 | address private newTokenControllerAddress; 30 | 31 | uint256 private messageTransmitterV2OwnerPrivateKey; 32 | uint256 private tokenMessengerV2OwnerPrivateKey; 33 | uint256 private tokenMinterV2OwnerPrivateKey; 34 | 35 | address private messageTransmitterV2NewOwnerAddress; 36 | address private tokenMessengerV2NewOwnerAddress; 37 | address private tokenMinterV2NewOwnerAddress; 38 | 39 | function rotateMessageTransmitterV2Owner(uint256 privateKey) public { 40 | // load messageTransmitter 41 | MessageTransmitterV2 messageTransmitterV2 = MessageTransmitterV2( 42 | messageTransmitterV2ContractAddress 43 | ); 44 | 45 | vm.startBroadcast(privateKey); 46 | 47 | messageTransmitterV2.transferOwnership( 48 | messageTransmitterV2NewOwnerAddress 49 | ); 50 | 51 | vm.stopBroadcast(); 52 | } 53 | 54 | function rotateTokenMessengerV2Owner(uint256 privateKey) public { 55 | TokenMessengerV2 tokenMessengerV2 = TokenMessengerV2( 56 | tokenMessengerV2ContractAddress 57 | ); 58 | 59 | vm.startBroadcast(privateKey); 60 | 61 | tokenMessengerV2.transferOwnership(tokenMessengerV2NewOwnerAddress); 62 | 63 | vm.stopBroadcast(); 64 | } 65 | 66 | function rotateTokenControllerThenTokenMinterV2Owner( 67 | uint256 privateKey 68 | ) public { 69 | TokenMinterV2 tokenMinterV2 = TokenMinterV2( 70 | tokenMinterV2ContractAddress 71 | ); 72 | 73 | vm.startBroadcast(privateKey); 74 | 75 | tokenMinterV2.setTokenController(newTokenControllerAddress); 76 | 77 | tokenMinterV2.transferOwnership(tokenMinterV2NewOwnerAddress); 78 | 79 | vm.stopBroadcast(); 80 | } 81 | 82 | /** 83 | * @notice initialize variables from environment 84 | */ 85 | function setUp() public { 86 | messageTransmitterV2ContractAddress = vm.envAddress( 87 | "MESSAGE_TRANSMITTER_V2_CONTRACT_ADDRESS" 88 | ); 89 | 90 | tokenMessengerV2ContractAddress = vm.envAddress( 91 | "TOKEN_MESSENGER_V2_CONTRACT_ADDRESS" 92 | ); 93 | 94 | tokenMinterV2ContractAddress = vm.envAddress( 95 | "TOKEN_MINTER_V2_CONTRACT_ADDRESS" 96 | ); 97 | 98 | messageTransmitterV2OwnerPrivateKey = vm.envUint( 99 | "MESSAGE_TRANSMITTER_V2_OWNER_KEY" 100 | ); 101 | tokenMessengerV2OwnerPrivateKey = vm.envUint( 102 | "TOKEN_MESSENGER_V2_OWNER_KEY" 103 | ); 104 | tokenMinterV2OwnerPrivateKey = vm.envUint("TOKEN_MINTER_V2_OWNER_KEY"); 105 | 106 | messageTransmitterV2NewOwnerAddress = vm.envAddress( 107 | "MESSAGE_TRANSMITTER_V2_NEW_OWNER_ADDRESS" 108 | ); 109 | 110 | tokenMessengerV2NewOwnerAddress = vm.envAddress( 111 | "TOKEN_MESSENGER_V2_NEW_OWNER_ADDRESS" 112 | ); 113 | 114 | tokenMinterV2NewOwnerAddress = vm.envAddress( 115 | "TOKEN_MINTER_V2_NEW_OWNER_ADDRESS" 116 | ); 117 | 118 | newTokenControllerAddress = vm.envAddress( 119 | "NEW_TOKEN_CONTROLLER_ADDRESS" 120 | ); 121 | } 122 | 123 | /** 124 | * @notice main function that will be run by forge 125 | */ 126 | function run() public { 127 | rotateMessageTransmitterV2Owner(messageTransmitterV2OwnerPrivateKey); 128 | rotateTokenMessengerV2Owner(tokenMessengerV2OwnerPrivateKey); 129 | rotateTokenControllerThenTokenMinterV2Owner( 130 | tokenMinterV2OwnerPrivateKey 131 | ); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/messages/BurnMessage.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, Circle Internet Financial Limited. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | pragma solidity 0.7.6; 17 | 18 | import "@memview-sol/contracts/TypedMemView.sol"; 19 | 20 | /** 21 | * @title BurnMessage Library 22 | * @notice Library for formatted BurnMessages used by TokenMessenger. 23 | * @dev BurnMessage format: 24 | * Field Bytes Type Index 25 | * version 4 uint32 0 26 | * burnToken 32 bytes32 4 27 | * mintRecipient 32 bytes32 36 28 | * amount 32 uint256 68 29 | * messageSender 32 bytes32 100 30 | **/ 31 | library BurnMessage { 32 | using TypedMemView for bytes; 33 | using TypedMemView for bytes29; 34 | 35 | uint8 private constant VERSION_INDEX = 0; 36 | uint8 private constant VERSION_LEN = 4; 37 | uint8 private constant BURN_TOKEN_INDEX = 4; 38 | uint8 private constant BURN_TOKEN_LEN = 32; 39 | uint8 private constant MINT_RECIPIENT_INDEX = 36; 40 | uint8 private constant MINT_RECIPIENT_LEN = 32; 41 | uint8 private constant AMOUNT_INDEX = 68; 42 | uint8 private constant AMOUNT_LEN = 32; 43 | uint8 private constant MSG_SENDER_INDEX = 100; 44 | uint8 private constant MSG_SENDER_LEN = 32; 45 | // 4 byte version + 32 bytes burnToken + 32 bytes mintRecipient + 32 bytes amount + 32 bytes messageSender 46 | uint8 private constant BURN_MESSAGE_LEN = 132; 47 | 48 | /** 49 | * @notice Formats Burn message 50 | * @param _version The message body version 51 | * @param _burnToken The burn token address on source domain as bytes32 52 | * @param _mintRecipient The mint recipient address as bytes32 53 | * @param _amount The burn amount 54 | * @param _messageSender The message sender 55 | * @return Burn formatted message. 56 | */ 57 | function _formatMessage( 58 | uint32 _version, 59 | bytes32 _burnToken, 60 | bytes32 _mintRecipient, 61 | uint256 _amount, 62 | bytes32 _messageSender 63 | ) internal pure returns (bytes memory) { 64 | return 65 | abi.encodePacked( 66 | _version, 67 | _burnToken, 68 | _mintRecipient, 69 | _amount, 70 | _messageSender 71 | ); 72 | } 73 | 74 | /** 75 | * @notice Retrieves the burnToken from a DepositForBurn BurnMessage 76 | * @param _message The message 77 | * @return sourceToken address as bytes32 78 | */ 79 | function _getMessageSender(bytes29 _message) 80 | internal 81 | pure 82 | returns (bytes32) 83 | { 84 | return _message.index(MSG_SENDER_INDEX, MSG_SENDER_LEN); 85 | } 86 | 87 | /** 88 | * @notice Retrieves the burnToken from a DepositForBurn BurnMessage 89 | * @param _message The message 90 | * @return sourceToken address as bytes32 91 | */ 92 | function _getBurnToken(bytes29 _message) internal pure returns (bytes32) { 93 | return _message.index(BURN_TOKEN_INDEX, BURN_TOKEN_LEN); 94 | } 95 | 96 | /** 97 | * @notice Retrieves the mintRecipient from a BurnMessage 98 | * @param _message The message 99 | * @return mintRecipient 100 | */ 101 | function _getMintRecipient(bytes29 _message) 102 | internal 103 | pure 104 | returns (bytes32) 105 | { 106 | return _message.index(MINT_RECIPIENT_INDEX, MINT_RECIPIENT_LEN); 107 | } 108 | 109 | /** 110 | * @notice Retrieves the amount from a BurnMessage 111 | * @param _message The message 112 | * @return amount 113 | */ 114 | function _getAmount(bytes29 _message) internal pure returns (uint256) { 115 | return _message.indexUint(AMOUNT_INDEX, AMOUNT_LEN); 116 | } 117 | 118 | /** 119 | * @notice Retrieves the version from a Burn message 120 | * @param _message The message 121 | * @return version 122 | */ 123 | function _getVersion(bytes29 _message) internal pure returns (uint32) { 124 | return uint32(_message.indexUint(VERSION_INDEX, VERSION_LEN)); 125 | } 126 | 127 | /** 128 | * @notice Reverts if burn message is malformed or invalid length 129 | * @param _message The burn message as bytes29 130 | */ 131 | function _validateBurnMessageFormat(bytes29 _message) internal pure { 132 | require(_message.isValid(), "Malformed message"); 133 | require(_message.len() == BURN_MESSAGE_LEN, "Invalid message length"); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /docs/index.js: -------------------------------------------------------------------------------- 1 | require("dotenv").config(); 2 | const { Web3 } = require('web3') 3 | 4 | const tokenMessengerAbi = require('./abis/cctp/TokenMessenger.json'); 5 | const messageAbi = require('./abis/cctp/Message.json'); 6 | const usdcAbi = require('./abis/Usdc.json'); 7 | const messageTransmitterAbi = require('./abis/cctp/MessageTransmitter.json'); 8 | 9 | const waitForTransaction = async(web3, txHash) => { 10 | let transactionReceipt = await web3.eth.getTransactionReceipt(txHash); 11 | while(transactionReceipt != null && transactionReceipt.status === 'FALSE') { 12 | transactionReceipt = await web3.eth.getTransactionReceipt(txHash); 13 | await new Promise(r => setTimeout(r, 4000)); 14 | } 15 | return transactionReceipt; 16 | } 17 | 18 | const main = async() => { 19 | const web3 = new Web3(process.env.ETH_TESTNET_RPC); 20 | 21 | // Add ETH private key used for signing transactions 22 | const ethSigner = web3.eth.accounts.privateKeyToAccount(process.env.ETH_PRIVATE_KEY); 23 | web3.eth.accounts.wallet.add(ethSigner); 24 | 25 | // Add AVAX private key used for signing transactions 26 | const avaxSigner = web3.eth.accounts.privateKeyToAccount(process.env.AVAX_PRIVATE_KEY); 27 | web3.eth.accounts.wallet.add(avaxSigner); 28 | 29 | // Testnet Contract Addresses 30 | const ETH_TOKEN_MESSENGER_CONTRACT_ADDRESS = '0x9f3B8679c73C2Fef8b59B4f3444d4e156fb70AA5'; 31 | const USDC_ETH_CONTRACT_ADDRESS = '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238'; 32 | const ETH_MESSAGE_CONTRACT_ADDRESS = '0x80537e4e8bab73d21096baa3a8c813b45ca0b7c9'; 33 | const AVAX_MESSAGE_TRANSMITTER_CONTRACT_ADDRESS = '0xa9fb1b3009dcb79e2fe346c16a604b8fa8ae0a79'; 34 | 35 | // initialize contracts using address and ABI 36 | const ethTokenMessengerContract = new web3.eth.Contract(tokenMessengerAbi, ETH_TOKEN_MESSENGER_CONTRACT_ADDRESS, {from: ethSigner.address}); 37 | const usdcEthContract = new web3.eth.Contract(usdcAbi, USDC_ETH_CONTRACT_ADDRESS, {from: ethSigner.address}); 38 | const ethMessageContract = new web3.eth.Contract(messageAbi, ETH_MESSAGE_CONTRACT_ADDRESS, {from: ethSigner.address}); 39 | const avaxMessageTransmitterContract = new web3.eth.Contract(messageTransmitterAbi, AVAX_MESSAGE_TRANSMITTER_CONTRACT_ADDRESS, {from: avaxSigner.address}); 40 | 41 | // AVAX destination address 42 | const mintRecipient = process.env.RECIPIENT_ADDRESS; 43 | const destinationAddressInBytes32 = await ethMessageContract.methods.addressToBytes32(mintRecipient).call(); 44 | const AVAX_DESTINATION_DOMAIN = 1; 45 | 46 | // Amount that will be transferred 47 | const amount = process.env.AMOUNT; 48 | 49 | // STEP 1: Approve messenger contract to withdraw from our active eth address 50 | const approveTxGas = await usdcEthContract.methods.approve(ETH_TOKEN_MESSENGER_CONTRACT_ADDRESS, amount).estimateGas() 51 | const approveTx = await usdcEthContract.methods.approve(ETH_TOKEN_MESSENGER_CONTRACT_ADDRESS, amount).send({gas: approveTxGas}) 52 | const approveTxReceipt = await waitForTransaction(web3, approveTx.transactionHash); 53 | console.log('ApproveTxReceipt: ', approveTxReceipt) 54 | 55 | // STEP 2: Burn USDC 56 | const burnTxGas = await ethTokenMessengerContract.methods.depositForBurn(amount, AVAX_DESTINATION_DOMAIN, destinationAddressInBytes32, USDC_ETH_CONTRACT_ADDRESS).estimateGas(); 57 | const burnTx = await ethTokenMessengerContract.methods.depositForBurn(amount, AVAX_DESTINATION_DOMAIN, destinationAddressInBytes32, USDC_ETH_CONTRACT_ADDRESS).send({gas: burnTxGas}); 58 | const burnTxReceipt = await waitForTransaction(web3, burnTx.transactionHash); 59 | console.log('BurnTxReceipt: ', burnTxReceipt) 60 | 61 | // STEP 3: Retrieve message bytes from logs 62 | const transactionReceipt = await web3.eth.getTransactionReceipt(burnTx.transactionHash); 63 | const eventTopic = web3.utils.keccak256('MessageSent(bytes)') 64 | const log = transactionReceipt.logs.find((l) => l.topics[0] === eventTopic) 65 | const messageBytes = web3.eth.abi.decodeParameters(['bytes'], log.data)[0] 66 | const messageHash = web3.utils.keccak256(messageBytes); 67 | 68 | console.log(`MessageBytes: ${messageBytes}`) 69 | console.log(`MessageHash: ${messageHash}`) 70 | 71 | // STEP 4: Fetch attestation signature 72 | let attestationResponse = {status: 'pending'}; 73 | while(attestationResponse.status != 'complete') { 74 | const response = await fetch(`https://iris-api-sandbox.circle.com/attestations/${messageHash}`); 75 | attestationResponse = await response.json() 76 | await new Promise(r => setTimeout(r, 2000)); 77 | } 78 | 79 | const attestationSignature = attestationResponse.attestation; 80 | console.log(`Signature: ${attestationSignature}`) 81 | 82 | // STEP 5: Using the message bytes and signature recieve the funds on destination chain and address 83 | web3.setProvider(process.env.AVAX_TESTNET_RPC); // Connect web3 to AVAX testnet 84 | const receiveTxGas = await avaxMessageTransmitterContract.methods.receiveMessage(messageBytes, attestationSignature).estimateGas(); 85 | const receiveTx = await avaxMessageTransmitterContract.methods.receiveMessage(messageBytes, attestationSignature).send({gas: receiveTxGas}); 86 | const receiveTxReceipt = await waitForTransaction(web3, receiveTx.transactionHash); 87 | console.log('ReceiveTxReceipt: ', receiveTxReceipt) 88 | }; 89 | 90 | main() 91 | -------------------------------------------------------------------------------- /src/proxy/AdminUpgradableProxy.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Circle Internet Group, Inc. All rights reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | pragma solidity 0.7.6; 19 | 20 | import {UpgradeableProxy, Address} from "@openzeppelin/contracts/proxy/UpgradeableProxy.sol"; 21 | 22 | /** 23 | * @title AdminUpgradeableProxy 24 | * @notice This contract combines an upgradeable proxy with an authorization 25 | * mechanism for administrative tasks. 26 | * 27 | * @dev Forked from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/8e0296096449d9b1cd7c5631e917330635244c37/contracts/proxy/TransparentUpgradeableProxy.sol#L1 28 | * Modifications (10/1/2024): 29 | * - Remove ifAdmin modifier from admin() and implementation() and updated natspec. 30 | * - Update admin() and implementation() functions to be view functions. 31 | * - Pin Solidity to 0.7.6. 32 | * - Remove constructor visibility specifier. 33 | * - Remove overriden _beforeFallback() implementation. 34 | * - Bump constants, modifiers, and event declarations above constructor for consistency. 35 | * - Use "AdminUpgradableProxy" in revert string 36 | */ 37 | contract AdminUpgradableProxy is UpgradeableProxy { 38 | /** 39 | * @dev Emitted when the admin account has changed. 40 | */ 41 | event AdminChanged(address previousAdmin, address newAdmin); 42 | 43 | /** 44 | * @dev Storage slot with the admin of the contract. 45 | * This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is 46 | * validated in the constructor. 47 | */ 48 | bytes32 private constant _ADMIN_SLOT = 49 | 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; 50 | 51 | /** 52 | * @dev Modifier used internally that will delegate the call to the implementation unless the sender is the admin. 53 | */ 54 | modifier ifAdmin() { 55 | if (msg.sender == _admin()) { 56 | _; 57 | } else { 58 | _fallback(); 59 | } 60 | } 61 | 62 | /** 63 | * @dev Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and 64 | * optionally initialized with `_data`. 65 | */ 66 | constructor( 67 | address _logic, 68 | address admin_, 69 | bytes memory _data 70 | ) payable UpgradeableProxy(_logic, _data) { 71 | assert( 72 | _ADMIN_SLOT == 73 | bytes32(uint256(keccak256("eip1967.proxy.admin")) - 1) 74 | ); 75 | _setAdmin(admin_); 76 | } 77 | 78 | /** 79 | * @dev Returns the current admin. 80 | */ 81 | function admin() external view returns (address admin_) { 82 | admin_ = _admin(); 83 | } 84 | 85 | /** 86 | * @dev Returns the current implementation. 87 | */ 88 | function implementation() external view returns (address implementation_) { 89 | implementation_ = _implementation(); 90 | } 91 | 92 | /** 93 | * @dev Changes the admin of the proxy. 94 | * 95 | * Emits an {AdminChanged} event. 96 | * @dev Only the admin can call this function; other callers are delegated 97 | */ 98 | function changeAdmin(address newAdmin) external virtual ifAdmin { 99 | require( 100 | newAdmin != address(0), 101 | "AdminUpgradableProxy: new admin is the zero address" 102 | ); 103 | emit AdminChanged(_admin(), newAdmin); 104 | _setAdmin(newAdmin); 105 | } 106 | 107 | /** 108 | * @dev Upgrade the implementation of the proxy. 109 | * @dev Only the admin can call this function; other callers are delegated 110 | */ 111 | function upgradeTo(address newImplementation) external virtual ifAdmin { 112 | _upgradeTo(newImplementation); 113 | } 114 | 115 | /** 116 | * @dev Upgrade the implementation of the proxy, and then call a function from the new implementation as specified 117 | * by `data`, which should be an encoded function call. This is useful to initialize new storage variables in the 118 | * proxied contract. 119 | * @dev Only the admin can call this function; other callers are delegated 120 | */ 121 | function upgradeToAndCall( 122 | address newImplementation, 123 | bytes calldata data 124 | ) external payable virtual ifAdmin { 125 | _upgradeTo(newImplementation); 126 | Address.functionDelegateCall(newImplementation, data); 127 | } 128 | 129 | /** 130 | * @dev Returns the current admin. 131 | */ 132 | function _admin() internal view virtual returns (address adm) { 133 | bytes32 slot = _ADMIN_SLOT; 134 | assembly { 135 | adm := sload(slot) 136 | } 137 | } 138 | 139 | /** 140 | * @dev Stores a new address in the EIP1967 admin slot. 141 | */ 142 | function _setAdmin(address newAdmin) private { 143 | bytes32 slot = _ADMIN_SLOT; 144 | assembly { 145 | sstore(slot, newAdmin) 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /docs/abis/cctp/v2/Denylistable.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "function", 4 | "name": "acceptOwnership", 5 | "inputs": [], 6 | "outputs": [], 7 | "stateMutability": "nonpayable" 8 | }, 9 | { 10 | "type": "function", 11 | "name": "denylist", 12 | "inputs": [ 13 | { 14 | "name": "account", 15 | "type": "address", 16 | "internalType": "address" 17 | } 18 | ], 19 | "outputs": [], 20 | "stateMutability": "nonpayable" 21 | }, 22 | { 23 | "type": "function", 24 | "name": "denylister", 25 | "inputs": [], 26 | "outputs": [ 27 | { 28 | "name": "", 29 | "type": "address", 30 | "internalType": "address" 31 | } 32 | ], 33 | "stateMutability": "view" 34 | }, 35 | { 36 | "type": "function", 37 | "name": "isDenylisted", 38 | "inputs": [ 39 | { 40 | "name": "account", 41 | "type": "address", 42 | "internalType": "address" 43 | } 44 | ], 45 | "outputs": [ 46 | { 47 | "name": "", 48 | "type": "bool", 49 | "internalType": "bool" 50 | } 51 | ], 52 | "stateMutability": "view" 53 | }, 54 | { 55 | "type": "function", 56 | "name": "owner", 57 | "inputs": [], 58 | "outputs": [ 59 | { 60 | "name": "", 61 | "type": "address", 62 | "internalType": "address" 63 | } 64 | ], 65 | "stateMutability": "view" 66 | }, 67 | { 68 | "type": "function", 69 | "name": "pendingOwner", 70 | "inputs": [], 71 | "outputs": [ 72 | { 73 | "name": "", 74 | "type": "address", 75 | "internalType": "address" 76 | } 77 | ], 78 | "stateMutability": "view" 79 | }, 80 | { 81 | "type": "function", 82 | "name": "transferOwnership", 83 | "inputs": [ 84 | { 85 | "name": "newOwner", 86 | "type": "address", 87 | "internalType": "address" 88 | } 89 | ], 90 | "outputs": [], 91 | "stateMutability": "nonpayable" 92 | }, 93 | { 94 | "type": "function", 95 | "name": "unDenylist", 96 | "inputs": [ 97 | { 98 | "name": "account", 99 | "type": "address", 100 | "internalType": "address" 101 | } 102 | ], 103 | "outputs": [], 104 | "stateMutability": "nonpayable" 105 | }, 106 | { 107 | "type": "function", 108 | "name": "updateDenylister", 109 | "inputs": [ 110 | { 111 | "name": "newDenylister", 112 | "type": "address", 113 | "internalType": "address" 114 | } 115 | ], 116 | "outputs": [], 117 | "stateMutability": "nonpayable" 118 | }, 119 | { 120 | "type": "event", 121 | "name": "Denylisted", 122 | "inputs": [ 123 | { 124 | "name": "account", 125 | "type": "address", 126 | "indexed": true, 127 | "internalType": "address" 128 | } 129 | ], 130 | "anonymous": false 131 | }, 132 | { 133 | "type": "event", 134 | "name": "DenylisterChanged", 135 | "inputs": [ 136 | { 137 | "name": "oldDenylister", 138 | "type": "address", 139 | "indexed": true, 140 | "internalType": "address" 141 | }, 142 | { 143 | "name": "newDenylister", 144 | "type": "address", 145 | "indexed": true, 146 | "internalType": "address" 147 | } 148 | ], 149 | "anonymous": false 150 | }, 151 | { 152 | "type": "event", 153 | "name": "OwnershipTransferStarted", 154 | "inputs": [ 155 | { 156 | "name": "previousOwner", 157 | "type": "address", 158 | "indexed": true, 159 | "internalType": "address" 160 | }, 161 | { 162 | "name": "newOwner", 163 | "type": "address", 164 | "indexed": true, 165 | "internalType": "address" 166 | } 167 | ], 168 | "anonymous": false 169 | }, 170 | { 171 | "type": "event", 172 | "name": "OwnershipTransferred", 173 | "inputs": [ 174 | { 175 | "name": "previousOwner", 176 | "type": "address", 177 | "indexed": true, 178 | "internalType": "address" 179 | }, 180 | { 181 | "name": "newOwner", 182 | "type": "address", 183 | "indexed": true, 184 | "internalType": "address" 185 | } 186 | ], 187 | "anonymous": false 188 | }, 189 | { 190 | "type": "event", 191 | "name": "UnDenylisted", 192 | "inputs": [ 193 | { 194 | "name": "account", 195 | "type": "address", 196 | "indexed": true, 197 | "internalType": "address" 198 | } 199 | ], 200 | "anonymous": false 201 | } 202 | ] -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build test anvil anvil-test anvil-deploy cast-call cast-send clean 2 | 3 | FOUNDRY := docker run --rm foundry 4 | ANVIL := docker run -d -p 8545:8545 --name anvil --rm foundry 5 | 6 | build: 7 | docker build --no-cache -f Dockerfile -t foundry . 8 | 9 | test: 10 | @${FOUNDRY} "forge test -vv" 11 | 12 | simulate-deploy: 13 | forge script scripts/v1/deploy.s.sol:DeployScript --rpc-url ${RPC_URL} --sender ${SENDER} 14 | 15 | deploy: 16 | forge script scripts/v1/deploy.s.sol:DeployScript --rpc-url ${RPC_URL} --sender ${SENDER} --broadcast 17 | 18 | simulate-deploy-implementations-v2: 19 | forge script scripts/v2/DeployImplementationsV2.s.sol:DeployImplementationsV2Script --rpc-url ${RPC_URL} --sender ${SENDER} 20 | 21 | deploy-implementations-v2: 22 | forge script scripts/v2/DeployImplementationsV2.s.sol:DeployImplementationsV2Script --rpc-url ${RPC_URL} --sender ${SENDER} --private-key ${CREATE2_FACTORY_OWNER_KEY} --broadcast 23 | 24 | deploy-implementations-v2-unlocked: 25 | forge script scripts/v2/DeployImplementationsV2.s.sol:DeployImplementationsV2Script --rpc-url ${RPC_URL} --sender 0x0000000000000000000000000000000000000000 --broadcast --unlocked 26 | 27 | simulate-deploy-create2-factory: 28 | forge script scripts/DeployCreate2Factory.s.sol:DeployCreate2FactoryScript --rpc-url ${RPC_URL} --sender ${SENDER} 29 | 30 | deploy-create2-factory: 31 | forge script scripts/DeployCreate2Factory.s.sol:DeployCreate2FactoryScript --rpc-url ${RPC_URL} --sender ${SENDER} --broadcast --private-key ${CREATE2_FACTORY_OWNER_KEY} 32 | 33 | deploy-create2-factory-unlocked: 34 | forge script scripts/DeployCreate2Factory.s.sol:DeployCreate2FactoryScript --rpc-url ${RPC_URL} --sender 0x0000000000000000000000000000000000000000 --broadcast --unlocked 35 | 36 | simulate-deploy-proxies-v2: 37 | forge script scripts/v2/DeployProxiesV2.s.sol:DeployProxiesV2Script --rpc-url ${RPC_URL} --sender ${SENDER} 38 | 39 | deploy-proxies-v2: 40 | forge script scripts/v2/DeployProxiesV2.s.sol:DeployProxiesV2Script --rpc-url ${RPC_URL} --sender ${SENDER} --broadcast --private-key ${CREATE2_FACTORY_OWNER_KEY} 41 | 42 | deploy-proxies-v2-unlocked: 43 | forge script scripts/v2/DeployProxiesV2.s.sol:DeployProxiesV2Script --rpc-url ${RPC_URL} --sender 0x0000000000000000000000000000000000000000 --broadcast --unlocked 44 | 45 | simulate-setup-remote-resources-v2: 46 | forge script scripts/v2/SetupRemoteResourcesV2.s.sol:SetupRemoteResourcesV2Script --rpc-url ${RPC_URL} --sender ${SENDER} 47 | 48 | simulate-configure-token-minter-v2: 49 | forge script scripts/v2/DeployProxiesV2.s.sol:DeployProxiesV2Script --sig "configureTokenMinterV2()()" --rpc-url ${RPC_URL} --sender ${SENDER} 50 | 51 | configure-token-minter-v2: 52 | forge script scripts/v2/DeployProxiesV2.s.sol:DeployProxiesV2Script --sig "configureTokenMinterV2()()" --rpc-url ${RPC_URL} --sender ${SENDER} --broadcast 53 | 54 | setup-remote-resources-v2: 55 | forge script scripts/v2/SetupRemoteResourcesV2.s.sol:SetupRemoteResourcesV2Script --rpc-url ${RPC_URL} --sender ${SENDER} --broadcast 56 | 57 | simulate-rotate-keys-v2: 58 | forge script scripts/v2/RotateKeysV2.s.sol:RotateKeysV2Script --rpc-url ${RPC_URL} --sender ${SENDER} 59 | 60 | rotate-keys-v2: 61 | forge script scripts/v2/RotateKeysV2.s.sol:RotateKeysV2Script --rpc-url ${RPC_URL} --sender ${SENDER} --broadcast 62 | 63 | simulate-deploy-address-utils-external: 64 | forge script scripts/v2/DeployAddressUtilsExternal.s.sol:DeployAddressUtilsExternalScript --rpc-url ${RPC_URL} --sender ${SENDER} --private-keys ${CREATE2_FACTORY_OWNER_KEY} 65 | 66 | deploy-address-utils-external: 67 | forge script scripts/v2/DeployAddressUtilsExternal.s.sol:DeployAddressUtilsExternalScript --rpc-url ${RPC_URL} --sender ${SENDER} --broadcast --private-keys ${CREATE2_FACTORY_OWNER_KEY} 68 | 69 | deploy-address-utils-external-unlocked: 70 | forge script scripts/v2/DeployAddressUtilsExternal.s.sol:DeployAddressUtilsExternalScript --rpc-url ${RPC_URL} --sender 0x0000000000000000000000000000000000000000 --broadcast --unlocked 71 | 72 | anvil: 73 | docker rm -f anvil || true 74 | @${ANVIL} "anvil --host 0.0.0.0 -a 13 --code-size-limit 250000" 75 | 76 | anvil-test: anvil 77 | pip3 install -r requirements.txt 78 | python anvil/crosschainTransferIT.py 79 | 80 | anvil-test-v2: anvil 81 | pip3 install -r requirements.txt 82 | python anvil/crosschainTransferITV2.py 83 | 84 | anvil-impersonate: 85 | python ./scripts/impersonateAnvilAccounts.py $(ADDRESSES) --balance 10 86 | 87 | deploy-local: 88 | @docker exec anvil forge script anvil/scripts/${contract}.s.sol:${contract}Script --rpc-url http://localhost:8545 --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --broadcast 89 | 90 | cast-call: 91 | @docker exec anvil cast call ${contract_address} "${function}" --rpc-url http://localhost:8545 92 | 93 | cast-send: 94 | @docker exec anvil cast send ${contract_address} "${function}" --rpc-url http://localhost:8545 --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 95 | 96 | clean: 97 | @${FOUNDRY} "forge clean" 98 | 99 | analyze-message-transmitter: 100 | pip3 install mythril==0.24.8 101 | myth -v4 analyze src/MessageTransmitter.sol --solc-json mythril.config.json --solv 0.7.6 102 | 103 | analyze-message-transmitter-v2: 104 | pip3 install mythril==0.24.8 105 | myth -v4 analyze src/v2/MessageTransmitterV2.sol --solc-json mythril.config.json --solv 0.7.6 106 | 107 | analyze-token-messenger-minter: 108 | pip3 install mythril==0.24.8 109 | myth -v4 analyze src/TokenMessenger.sol --solc-json mythril.config.json --solv 0.7.6 110 | myth -v4 analyze src/TokenMinter.sol --solc-json mythril.config.json --solv 0.7.6 111 | myth -v4 analyze src/v2/TokenMessengerV2.sol --solc-json mythril.config.json --solv 0.7.6 112 | myth -v4 analyze src/v2/TokenMinterV2.sol --solc-json mythril.config.json --solv 0.7.6 113 | -------------------------------------------------------------------------------- /src/messages/v2/BurnMessageV2.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Circle Internet Group, Inc. All rights reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | pragma solidity 0.7.6; 19 | 20 | import {TypedMemView} from "@memview-sol/contracts/TypedMemView.sol"; 21 | import {BurnMessage} from "../BurnMessage.sol"; 22 | 23 | /** 24 | * @title BurnMessageV2 Library 25 | * @notice Library for formatted V2 BurnMessages used by TokenMessengerV2. 26 | * @dev BurnMessageV2 format: 27 | * Field Bytes Type Index 28 | * version 4 uint32 0 29 | * burnToken 32 bytes32 4 30 | * mintRecipient 32 bytes32 36 31 | * amount 32 uint256 68 32 | * messageSender 32 bytes32 100 33 | * maxFee 32 uint256 132 34 | * feeExecuted 32 uint256 164 35 | * expirationBlock 32 uint256 196 36 | * hookData dynamic bytes 228 37 | * @dev Additions from v1: 38 | * - maxFee 39 | * - feeExecuted 40 | * - expirationBlock 41 | * - hookData 42 | **/ 43 | library BurnMessageV2 { 44 | using TypedMemView for bytes; 45 | using TypedMemView for bytes29; 46 | using BurnMessage for bytes29; 47 | 48 | // Field indices 49 | uint8 private constant MAX_FEE_INDEX = 132; 50 | uint8 private constant FEE_EXECUTED_INDEX = 164; 51 | uint8 private constant EXPIRATION_BLOCK_INDEX = 196; 52 | uint8 private constant HOOK_DATA_INDEX = 228; 53 | 54 | uint256 private constant EMPTY_FEE_EXECUTED = 0; 55 | uint256 private constant EMPTY_EXPIRATION_BLOCK = 0; 56 | 57 | /** 58 | * @notice Formats a V2 burn message 59 | * @param _version The message body version 60 | * @param _burnToken The burn token address on the source domain, as bytes32 61 | * @param _mintRecipient The mint recipient address as bytes32 62 | * @param _amount The burn amount 63 | * @param _messageSender The message sender 64 | * @param _maxFee The maximum fee to be paid on destination domain 65 | * @param _hookData Optional hook data for processing on the destination domain 66 | * @return Formatted message bytes. 67 | */ 68 | function _formatMessageForRelay( 69 | uint32 _version, 70 | bytes32 _burnToken, 71 | bytes32 _mintRecipient, 72 | uint256 _amount, 73 | bytes32 _messageSender, 74 | uint256 _maxFee, 75 | bytes calldata _hookData 76 | ) internal pure returns (bytes memory) { 77 | return 78 | abi.encodePacked( 79 | _version, 80 | _burnToken, 81 | _mintRecipient, 82 | _amount, 83 | _messageSender, 84 | _maxFee, 85 | EMPTY_FEE_EXECUTED, 86 | EMPTY_EXPIRATION_BLOCK, 87 | _hookData 88 | ); 89 | } 90 | 91 | // @notice Returns _message's version field 92 | function _getVersion(bytes29 _message) internal pure returns (uint32) { 93 | return _message._getVersion(); 94 | } 95 | 96 | // @notice Returns _message's burnToken field 97 | function _getBurnToken(bytes29 _message) internal pure returns (bytes32) { 98 | return _message._getBurnToken(); 99 | } 100 | 101 | // @notice Returns _message's mintRecipient field 102 | function _getMintRecipient( 103 | bytes29 _message 104 | ) internal pure returns (bytes32) { 105 | return _message._getMintRecipient(); 106 | } 107 | 108 | // @notice Returns _message's amount field 109 | function _getAmount(bytes29 _message) internal pure returns (uint256) { 110 | return _message._getAmount(); 111 | } 112 | 113 | // @notice Returns _message's messageSender field 114 | function _getMessageSender( 115 | bytes29 _message 116 | ) internal pure returns (bytes32) { 117 | return _message._getMessageSender(); 118 | } 119 | 120 | // @notice Returns _message's maxFee field 121 | function _getMaxFee(bytes29 _message) internal pure returns (uint256) { 122 | return _message.indexUint(MAX_FEE_INDEX, 32); 123 | } 124 | 125 | // @notice Returns _message's feeExecuted field 126 | function _getFeeExecuted(bytes29 _message) internal pure returns (uint256) { 127 | return _message.indexUint(FEE_EXECUTED_INDEX, 32); 128 | } 129 | 130 | // @notice Returns _message's expirationBlock field 131 | function _getExpirationBlock( 132 | bytes29 _message 133 | ) internal pure returns (uint256) { 134 | return _message.indexUint(EXPIRATION_BLOCK_INDEX, 32); 135 | } 136 | 137 | // @notice Returns _message's hookData field 138 | function _getHookData(bytes29 _message) internal pure returns (bytes29) { 139 | return 140 | _message.slice( 141 | HOOK_DATA_INDEX, 142 | _message.len() - HOOK_DATA_INDEX, 143 | 0 144 | ); 145 | } 146 | 147 | /** 148 | * @notice Reverts if burn message is malformed or invalid length 149 | * @param _message The burn message as bytes29 150 | */ 151 | function _validateBurnMessageFormat(bytes29 _message) internal pure { 152 | require(_message.isValid(), "Malformed message"); 153 | require( 154 | _message.len() >= HOOK_DATA_INDEX, 155 | "Invalid burn message: too short" 156 | ); 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/roles/v2/Denylistable.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Circle Internet Group, Inc. All rights reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | pragma solidity 0.7.6; 19 | 20 | import {Ownable2Step} from "../Ownable2Step.sol"; 21 | 22 | /** 23 | * @title Denylistable 24 | * @notice Contract that allows the management and application of a denylist 25 | */ 26 | abstract contract Denylistable is Ownable2Step { 27 | // ============ Events ============ 28 | /** 29 | * @notice Emitted when the denylister is updated 30 | * @param oldDenylister Address of the previous Denylister 31 | * @param newDenylister Address of the new Denylister 32 | */ 33 | event DenylisterChanged( 34 | address indexed oldDenylister, 35 | address indexed newDenylister 36 | ); 37 | 38 | /** 39 | * @notice Emitted when `account` is added to the denylist 40 | * @param account Address added to the denylist 41 | */ 42 | event Denylisted(address indexed account); 43 | 44 | /** 45 | * @notice Emitted when `account` is removed from the denylist 46 | * @param account Address removed from the denylist 47 | */ 48 | event UnDenylisted(address indexed account); 49 | 50 | // ============ Constants ============ 51 | // A true boolean representation in uint256 52 | uint256 private constant _TRUE = 1; 53 | 54 | // A false boolean representation in uint256 55 | uint256 private constant _FALSE = 0; 56 | 57 | // ============ State Variables ============ 58 | // The currently set denylister 59 | address internal _denylister; 60 | 61 | // A mapping indicating whether an account is on the denylist. 1 indicates that an 62 | // address is on the denylist; 0 otherwise. 63 | mapping(address => uint256) internal _denylist; 64 | 65 | /** 66 | * @dev This empty reserved space is put in place to allow future versions to add new 67 | * variables without shifting down storage in the inheritance chain. 68 | * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps 69 | */ 70 | uint256[20] private __gap; 71 | 72 | // ============ Modifiers ============ 73 | /** 74 | * @dev Throws if called by any account other than the denylister. 75 | */ 76 | modifier onlyDenylister() { 77 | require( 78 | msg.sender == _denylister, 79 | "Denylistable: caller is not denylister" 80 | ); 81 | _; 82 | } 83 | 84 | /** 85 | * @dev Performs denylist checks on the msg.sender and tx.origin addresses 86 | */ 87 | modifier notDenylistedCallers() { 88 | _requireNotDenylisted(msg.sender); 89 | if (msg.sender != tx.origin) { 90 | _requireNotDenylisted(tx.origin); 91 | } 92 | _; 93 | } 94 | 95 | // ============ External Functions ============ 96 | /** 97 | * @notice Updates the currently set Denylister 98 | * @dev Reverts if not called by the Owner 99 | * @dev Reverts if the new denylister address is the zero address 100 | * @param newDenylister The new denylister address 101 | */ 102 | function updateDenylister(address newDenylister) external onlyOwner { 103 | _updateDenylister(newDenylister); 104 | } 105 | 106 | /** 107 | * @notice Adds an address to the denylist 108 | * @param account Address to add to the denylist 109 | */ 110 | function denylist(address account) external onlyDenylister { 111 | _denylist[account] = _TRUE; 112 | emit Denylisted(account); 113 | } 114 | 115 | /** 116 | * @notice Removes an address from the denylist 117 | * @param account Address to remove from the denylist 118 | */ 119 | function unDenylist(address account) external onlyDenylister { 120 | _denylist[account] = _FALSE; 121 | emit UnDenylisted(account); 122 | } 123 | 124 | /** 125 | * @notice Returns the currently set Denylister 126 | * @return Denylister address 127 | */ 128 | function denylister() external view returns (address) { 129 | return _denylister; 130 | } 131 | 132 | /** 133 | * @notice Returns whether an address is currently on the denylist 134 | * @param account Address to check 135 | * @return True if the account is on the deny list and false if the account is not. 136 | */ 137 | function isDenylisted(address account) external view returns (bool) { 138 | return _denylist[account] == _TRUE; 139 | } 140 | 141 | // ============ Internal Utils ============ 142 | /** 143 | * @notice Updates the currently set denylister 144 | * @param _newDenylister The new denylister address 145 | */ 146 | function _updateDenylister(address _newDenylister) internal { 147 | require( 148 | _newDenylister != address(0), 149 | "Denylistable: new denylister is the zero address" 150 | ); 151 | address _oldDenylister = _denylister; 152 | _denylister = _newDenylister; 153 | emit DenylisterChanged(_oldDenylister, _newDenylister); 154 | } 155 | 156 | /** 157 | * @notice Checks an address against the denylist 158 | * @dev Reverts if address is on the denylist 159 | */ 160 | function _requireNotDenylisted(address _address) internal view { 161 | require( 162 | _denylist[_address] == _FALSE, 163 | "Denylistable: account is on denylist" 164 | ); 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /test/v2/Initializable.t.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Circle Internet Group, Inc. All rights reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | pragma solidity 0.7.6; 19 | pragma abicoder v2; 20 | 21 | import {Initializable} from "../../src/proxy/Initializable.sol"; 22 | import {MockInitializableImplementation} from "../mocks/MockInitializableImplementation.sol"; 23 | import {Test} from "forge-std/Test.sol"; 24 | 25 | contract InitializableTest is Test { 26 | MockInitializableImplementation private impl; 27 | 28 | event Initialized(uint64 version); 29 | 30 | struct InitializableStorage { 31 | uint64 _initialized; 32 | bool _initializing; 33 | } 34 | 35 | bytes32 private constant INITIALIZABLE_STORAGE = 36 | 0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00; 37 | 38 | function setUp() public { 39 | impl = new MockInitializableImplementation(); 40 | } 41 | 42 | function test_canBeInitializedToNextIncrementedVersionFromZero() public { 43 | assertEq(uint256(impl.initializedVersion()), 0); 44 | 45 | // Upgrade 0 -> 1 46 | vm.expectEmit(true, true, true, true); 47 | emit Initialized(1); 48 | impl.initialize(address(10), 1); 49 | assertEq(uint256(impl.initializedVersion()), 1); 50 | } 51 | 52 | function test_canBeReinitializedToNextIncrementedVersionFromNonZero() 53 | public 54 | { 55 | impl.initialize(address(10), 1); 56 | assertEq(uint256(impl.initializedVersion()), 1); 57 | 58 | // Upgrade 1 -> 2 59 | vm.expectEmit(true, true, true, true); 60 | emit Initialized(2); 61 | impl.initializeV2(); 62 | assertEq(uint256(impl.initializedVersion()), 2); 63 | } 64 | 65 | function test_canJumpToLaterVersionFromZero() public { 66 | assertEq(uint256(impl.initializedVersion()), 0); 67 | 68 | // Upgrade 0 -> 2 69 | vm.expectEmit(true, true, true, true); 70 | emit Initialized(2); 71 | impl.initializeV2(); 72 | assertEq(uint256(impl.initializedVersion()), 2); 73 | } 74 | 75 | function test_canJumpToLaterVersionFromNonZero() public { 76 | impl.initialize(address(10), 1); 77 | assertEq(uint256(impl.initializedVersion()), 1); 78 | 79 | // Upgrade 1 -> 3 80 | vm.expectEmit(true, true, true, true); 81 | emit Initialized(3); 82 | impl.initializeV3(); 83 | assertEq(uint256(impl.initializedVersion()), 3); 84 | } 85 | 86 | function test_revertsIfInitializerIsCalledTwice() public { 87 | impl.initialize(address(10), 1); 88 | 89 | vm.expectRevert("Initializable: invalid initialization"); 90 | impl.initialize(address(10), 1); 91 | } 92 | 93 | function test_revertsIfReinitializerIsCalledTwice() public { 94 | impl.initializeV2(); 95 | 96 | vm.expectRevert("Initializable: invalid initialization"); 97 | impl.initializeV2(); 98 | } 99 | 100 | function test_revertsIfInitializersAreDisabled() public { 101 | impl.disableInitializers(); 102 | 103 | vm.expectRevert("Initializable: invalid initialization"); 104 | impl.initialize(address(10), 1); 105 | } 106 | 107 | function testDisableInitializers_revertsIfInitializing() public { 108 | // Set the initializing storage slot to 'true' directly 109 | _setInitializableStorage(impl.initializedVersion(), true); 110 | 111 | // Sanity check 112 | assertTrue(impl.initializing()); 113 | 114 | vm.expectRevert("Initializable: invalid initialization"); 115 | impl.disableInitializers(); 116 | } 117 | 118 | function test_revertsIfDowngraded() public { 119 | impl.initializeV3(); 120 | assertEq(uint256(impl.initializedVersion()), 3); 121 | 122 | // Downgrade 3 -> 2 123 | vm.expectRevert("Initializable: invalid initialization"); 124 | impl.initializeV2(); 125 | 126 | assertEq(uint256(impl.initializedVersion()), 3); 127 | } 128 | 129 | function testOnlyInitializing_revertsIfCalledOutsideOfInitialization() 130 | public 131 | { 132 | assertFalse(impl.initializing()); 133 | 134 | vm.expectRevert("Initializable: not initializing"); 135 | impl.supportingInitializer(); 136 | } 137 | 138 | function testOnlyInitializing_succeedsIfCalledWhileInitializing() public { 139 | // Set 'initializing' to true directly 140 | _setInitializableStorage(impl.initializedVersion(), true); 141 | assertTrue(impl.initializing()); 142 | 143 | // Should be callable now 144 | impl.supportingInitializer(); 145 | } 146 | 147 | // Test utils 148 | 149 | function _setInitializableStorage( 150 | uint64 _initializedVersion, 151 | bool _initializing 152 | ) internal { 153 | // Write it to a slot, and then copy over the slot contents to the implementation address 154 | // There might be a better way to do this 155 | InitializableStorage storage $; 156 | assembly { 157 | $.slot := INITIALIZABLE_STORAGE 158 | } 159 | $._initialized = _initializedVersion; 160 | $._initializing = _initializing; 161 | 162 | // Copy over slot contents to implementation 163 | vm.store( 164 | address(impl), 165 | INITIALIZABLE_STORAGE, 166 | vm.load(address(this), INITIALIZABLE_STORAGE) 167 | ); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /scripts/v2/DeployImplementationsV2.s.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Circle Internet Group, Inc. All rights reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | pragma solidity 0.7.6; 19 | pragma abicoder v2; 20 | 21 | import {Script} from "forge-std/Script.sol"; 22 | import {AdminUpgradableProxy} from "../../src/proxy/AdminUpgradableProxy.sol"; 23 | import {TokenMessengerV2} from "../../src/v2/TokenMessengerV2.sol"; 24 | import {TokenMinter, TokenMinterV2} from "../../src/v2/TokenMinterV2.sol"; 25 | import {MessageTransmitterV2} from "../../src/v2/MessageTransmitterV2.sol"; 26 | import {Create2Factory} from "../../src/v2/Create2Factory.sol"; 27 | import {Ownable2Step} from "../../src/roles/Ownable2Step.sol"; 28 | import {SALT_MESSAGE_TRANSMITTER, SALT_TOKEN_MESSENGER, SALT_TOKEN_MINTER} from "./Salts.sol"; 29 | 30 | contract DeployImplementationsV2Script is Script { 31 | // Expose for tests 32 | MessageTransmitterV2 public messageTransmitterV2; 33 | TokenMessengerV2 public tokenMessengerV2; 34 | TokenMinterV2 public tokenMinterV2; 35 | address public expectedMessageTransmitterV2ProxyAddress; 36 | 37 | address private factoryAddress; 38 | address private tokenMinterOwnerAddress; 39 | uint256 private tokenMinterOwnerKey; 40 | address private tokenControllerAddress; 41 | uint32 private messageBodyVersion; 42 | uint32 private version; 43 | uint32 private domain; 44 | 45 | function deployImplementationsV2() 46 | private 47 | returns (MessageTransmitterV2, TokenMinterV2, TokenMessengerV2) 48 | { 49 | // Calculate MessageTransmitterV2 proxy address 50 | expectedMessageTransmitterV2ProxyAddress = vm.computeCreate2Address( 51 | SALT_MESSAGE_TRANSMITTER, 52 | keccak256( 53 | abi.encodePacked( 54 | type(AdminUpgradableProxy).creationCode, 55 | abi.encode(factoryAddress, factoryAddress, "") 56 | ) 57 | ), 58 | factoryAddress 59 | ); 60 | 61 | Create2Factory factory = Create2Factory(factoryAddress); 62 | 63 | // Start recording transactions 64 | vm.startBroadcast(factory.owner()); 65 | 66 | // Deploy MessageTransmitterV2 implementation 67 | messageTransmitterV2 = MessageTransmitterV2( 68 | factory.deploy( 69 | 0, 70 | SALT_MESSAGE_TRANSMITTER, 71 | abi.encodePacked( 72 | type(MessageTransmitterV2).creationCode, 73 | abi.encode(domain, version) 74 | ) 75 | ) 76 | ); 77 | 78 | // Deploy TokenMessengerV2 implementation 79 | tokenMessengerV2 = TokenMessengerV2( 80 | factory.deploy( 81 | 0, 82 | SALT_TOKEN_MESSENGER, 83 | abi.encodePacked( 84 | type(TokenMessengerV2).creationCode, 85 | abi.encode( 86 | expectedMessageTransmitterV2ProxyAddress, 87 | messageBodyVersion 88 | ) 89 | ) 90 | ) 91 | ); 92 | 93 | // Since the TokenMinter sets the msg.sender of the deployment to be 94 | // the Owner, we'll need to rotate it from the Create2Factory atomically. 95 | // But first we rotate the tokenController, since only the Owner can do that 96 | bytes memory tokenMinterTokenControllerRotation = abi 97 | .encodeWithSelector( 98 | TokenMinter.setTokenController.selector, 99 | tokenControllerAddress 100 | ); 101 | bytes memory tokenMinterOwnershipRotation = abi.encodeWithSelector( 102 | Ownable2Step.transferOwnership.selector, 103 | tokenMinterOwnerAddress 104 | ); 105 | bytes[] memory tokenMinterMultiCallData = new bytes[](2); 106 | tokenMinterMultiCallData[0] = tokenMinterTokenControllerRotation; 107 | tokenMinterMultiCallData[1] = tokenMinterOwnershipRotation; 108 | 109 | // Deploy TokenMinter 110 | tokenMinterV2 = TokenMinterV2( 111 | factory.deployAndMultiCall( 112 | 0, 113 | SALT_TOKEN_MINTER, 114 | abi.encodePacked( 115 | type(TokenMinterV2).creationCode, 116 | abi.encode(address(factory)) 117 | ), 118 | tokenMinterMultiCallData 119 | ) 120 | ); 121 | 122 | // Stop recording transactions 123 | vm.stopBroadcast(); 124 | 125 | // Accept the TokenMinter 2-step ownership 126 | vm.startBroadcast(tokenMinterOwnerKey); 127 | tokenMinterV2.acceptOwnership(); 128 | vm.stopBroadcast(); 129 | 130 | return (messageTransmitterV2, tokenMinterV2, tokenMessengerV2); 131 | } 132 | 133 | /** 134 | * @notice initialize variables from environment 135 | */ 136 | function setUp() public { 137 | factoryAddress = vm.envAddress("CREATE2_FACTORY_CONTRACT_ADDRESS"); 138 | tokenMinterOwnerKey = vm.envUint("TOKEN_MINTER_V2_OWNER_KEY"); 139 | tokenMinterOwnerAddress = vm.addr(tokenMinterOwnerKey); 140 | tokenControllerAddress = vm.envAddress("TOKEN_CONTROLLER_ADDRESS"); 141 | domain = uint32(vm.envUint("DOMAIN")); 142 | messageBodyVersion = uint32(vm.envUint("MESSAGE_BODY_VERSION")); 143 | version = uint32(vm.envUint("VERSION")); 144 | } 145 | 146 | /** 147 | * @notice main function that will be run by forge 148 | */ 149 | function run() public { 150 | ( 151 | messageTransmitterV2, 152 | tokenMinterV2, 153 | tokenMessengerV2 154 | ) = deployImplementationsV2(); 155 | } 156 | } 157 | --------------------------------------------------------------------------------