Table of Contents
Summary
- Type
- DeFi
- Timeline
- From 2025-03-13
- To 2025-03-18
Languages:
Solidity
- Total Issues
- 4 (3 resolved, 1 partially resolved)
- Critical Severity Issues
- 0 (0 resolved)
- High Severity Issues
- 0 (0 resolved)
- Medium Severity Issues
- 0 (0 resolved)
- Low Severity Issues
- 2 (1 resolved, 1 partially resolved)
- Notes & Additional Information
- 2 (2 resolved)
- Client Reported Issues
- 0 (0 resolved)
Scope
We audited the across-protocol/contracts repository.
In scope were the changes made to the following files:
contracts
├── Linea_SpokePool.sol
├── chain-adapters/
│ └── Linea_Adapter.sol
├── libraries/
│ └── CircleCCTPAdapter.sol
└── external/
└── interfaces/
└── CCTPInterfaces.sol
in the pull request #921 with the final commit 0720878
.
We have additionally reviewed the e5310e0
commit.
System Overview
The Across protocol is an intent-based bridging protocol which enables fast token transfers between different blockcahins. For more details on how the protocol works, please refer to one of our previous audit reports.
Summary of Changes
In order to bridge USDC tokens between Ethereum and another blockchains supporting the Circle CCTP protocol, both the HubPool and SpokePools were utilizing the CircleCCTPAdapter
contract, which implements the USDC bridging logic using the first version (V1) of the CCTP protocol.
However, the Linea blockchain does not support the V1 version of CCTP and will only support the V2 version, which uses a different set of contracts and it results in a different API being exposed to the users. As a result, in order to support USDC transfers to and from the Linea blockchain, it has been necessary to adjust the existing contracts in Across so that they are able to interact with CCTP through the new API.
The changes involved small modifications of the CircleCCTPAdapter
, Linea_Adapter
and Linea_SpokePool
contracts and an introduction of the new ITokenMessengerV2
interface in order to facilitate communication with the second version of CCTP TokenMessenger contract.
Security Model and Trust Assumptions
The audit has been focused on the specific changes made to the contracts in order to integrate with CCTP V2 on Linea. As such, it has been restricted only to a small part of the codebase and has been conducted under the following trust assumptions.
Throughout the audit we assumed that all on-chain and off-chain components the in-scope code integrates with behave correctly and as expected. In particular, that the CCTP protocol works correctly and reliably transfers desired USDC amounts between Ethereum and Linea blockchains according to its documentation.
Furthermore, we assumed that the in-scope contracts will be correctly deployed and initialized with the correct parameters, which is crucial for the entire protocol to operate correctly.
Finally, the Linea CCTP Domain ID has not been officially announced at the time of the audit, although it was very likely that it would be equal to 11. Hence, it has been assumed that the constant reflecting it in the code is initialized correctly.
Privileged Roles
There have not been any new privileged roles added to the protocol in the pull request being reviewed.
Throughout the audit we assumed that all privileged entities already existing in the Across protocol will behave honestly and in the best interest of the protocol and its users.
Low Severity
Check For CCTP Version Is Not Fully Reliable
In order to determine the CCTP version which will be used for bridging USDC, the constructor of the CircleCCTPAdapter
contract performs a low level call to the feeRecipient
function of a CCTP TokenMessenger. The check relies on the fact that this function is not present in V1 contracts, but is present in V2 contracts and returns a non-zero address.
However, the check being performed is not fully reliable for the reasons enumerated below.
The values returned by the functions in Solidity are abi.encode
d, which means that the address returned by the feeRecipient
function will be padded to 32 bytes with 0s at the beginning. Furthermore, casting a bytes
object to bytes20
will return the first 20 bytes of that object, which will include 12 bytes of padding and as a result, only the first 8 bytes of the returned address will be taken into account in this check. In case when the first 8 bytes of the returned feeRecipient
are equal to 0, the contract will incorrectly assume that it should use CCTP V1.
Furthermore, while it is checked that the call succeeded, there is no verification that the return data is of correct size. As a result, the check can succeed in case when the return data is of a different size. In particular, for the return data of 0 length, the correctness of this check relies on the cast from an empty bytes
object to bytes20
always returning 0.
Finally, a contract to be called could still have a fallback function which returns 32 bytes of data. In case when at least 1 of the last 20 bytes of that data is nonzero, the check will still succeed even if the target contract does not implement the feeRecipient
function.
Consider casting the feeRecipient
to uint160
before casting it to address in order to retrieve the full address. Moreover, consider validating the length of the data returned by the low level call in order to better handle the situations when data with unexpected size is returned. Furthermore, consider implementing a more robust mechanism of determining the CCTP version to be used.
Update: Partially resolved in pull request #921 at commit d9d4707. The casting of the data returned from a low level call has been fixed and additional verification has been added to the length of the returned data. It is still possible that the check for the CCTP version will not determine the correct version, but this scenario is very unlikely and the risk has been accepted by the Risk Labs team.
Insufficient Documentation
The _transferUsdc
function of the CircleCCTPAdapter
contract is responsible for bridging USDC between different blockchains using the CCTP protocol. In order to do this, it queries the CCTPTokenMessenger
contract for the TokenMinter's address, determines the current burn limit per message and splits USDC deposits if necessary.
However, the cctpTokenMessenger
variable used to retrieve the minter's address, is of type ITokenMessenger
, which represents the version 1 of the TokenMessenger
contract, but it is possible that in reality it points to the TokenMessengerV2
contract. Since both TokenMessenger
and TokenMessengerV2
contracts define the localMinter
function being used, the function call will succeed for both cases, but this implicit behaviour could be reflected in the comments. Furthermore, the localMinter
being returned by this function is either of type ITokenMinter
or ITokenMinterV2
, depending on the version of TokenMessenger which is called. While both versions of the TokenMinter
contract expose the burnLimitsPerMessage
function, this fact is not obvious and could be documented as well.
Consider improving the documentation inside the _transferUsdc
function in order to improve readability and maintainability of the codebase.
Update: Resolved in pull request #921 at commit d9d4707.
Notes & Additional Information
Misleading Comments
Throughout the codebase, several instances of misleading comments were identified: - The comment states that the minFinalityThreshold
parameter can be set to 20 000
for a standard transfer, while the correct value is 2 000
. - The comment refers to an l1Token
, however the code checks whether l2TokenAddress
is the address of usdcToken
. Therefore, in the comment, l1Token
should be changed to l2Token
.
Consider correcting the aforementioned comments to improve the overall clarity and readability of the codebase.
Update: Resolved in pull request #921 at commit b135917.
Typographical Errors
Throughout the codebase, several instances of typographical errors were identified:
In the CircleCCTPAdapter.sol
file: - In line 70, "its" should be "it is". - In line 116, "to bridged" should be "to bridge". - In line 124, there is an extra space that could be removed at the beginning of the line.
Consider fixing all instances of typographical errors in order to improve the readability of the codebase.
Update: Resolved in pull request #921 at commit 63231d9.
Conclusion
The changes added to the code enable the contracts to bridge USDC to and from the Linea blockchain utilizing the second version of the CCTP protocol, while still allowing to use the first version of the protocol for another blockchains.
Throughout the audit, there have not been any significant issues identified.
The Risk Labs team has been very helpful throughout the engagement, answering all our questions promptly and in a great detail.