Skip to content

SVM Spoke Bugfix Review

Table of Contents

Summary

Type
DeFi
Timeline
From 2025-06-16
To 2025-06-18
Languages
Rust
Total Issues
1 (1 resolved)
Critical Severity Issues
0 (0 resolved)
High Severity Issues
0 (0 resolved)
Medium Severity Issues
0 (0 resolved)
Low Severity Issues
0 (0 resolved)
Notes & Additional Information
1 (1 resolved)

Scope

OpenZeppelin audited pull request #1043 and pull request #1045 of the across-protocol/contracts repository.

In scope were the following files:

 programs
└── svm-spoke
    └── src
        ├── error.rs
        ├── lib.rs
        ├── event.rs
        ├── common
        │   └── relay_data.rs
        ├── instructions
        │   └── deposit.rs
        └── utils
            └── delegate_utils.rs

Overview

On June 11th, 2025, the Across team informed OpenZeppelin about a vulnerability in the code that handles cross-chain transfers between Solana and EVM-based chains. It was identified that the u64 data type used by the Solana Token Program was insufficient to handle amounts specified from Ethereum that use uint256, which would severely limit the capabilities of cross-chain interactions. OpenZeppelin had previously audited the code in this report. Subsequently, a review was conducted to validate the correctness and security of the fix that had been implemented to address this issue (pull request #1045). An additional unrelated update to the code disallowing deposits with output token set to 0x0 was also reviewed (pull request #1043).

Reported Issues

Across relies on events to be fired during the deposit and fill actions so that the necessary actions can be taken by actors within the system. The issue is related to the amount fields in deposit and fill actions which are of insufficient size to store a large portion of token amounts corresponding to EVM chains. The Solana token program uses 64-bit integers to store token amounts, and so Solana tokens typically use 6-9 decimals in order to fit the total supply within this type. This is in contrast to EVM chains where many tokens are conventionally 18 decimals, stored in a 256-bit integer. Therefore, input and output amounts intended for EVM chains that exceed the 64-bit space will overflow and fail to be processed by the SVM spoke program. This discrepancy can cause the following impacts:

  1. Limits deposit output_amount: During a deposit to the SVM spoke, the user specifies the output_amount they would like to receive on the destination chain. If this amount is 2**64 or larger (approximately 18.4e18) they will not be able to submit the deposit request.
  2. Creates unfillable requests on the SVM spoke: When a relayer picks up a fill request from an EVM chain, the data includes an input_amount which needs to be submitted to the fill function on the SVM spoke. However, since the accepted type is u64, if the deposit from the source chain includes an input_amount of 2**64 or larger, the function will not be callable, and the request will be unfillable (and eventually refunded).

As this issue prevents a large number of intended transfers from happening to/from Solana, the review team estimates this issue to be of critical severity.

The fix was to make the output_amount in the deposit function a 32-byte array, i.e., [u8; 32], in order to align with output amounts on EVM chains. Similarly, the input_amount of the RelayData struct was changed to [u8; 32] in order to process data coming from EVM chains on the Solana side. Note that the input_amount in deposits is a Solana token (likewise, so is the output_amount in RelayData), and as such a u64 remains sufficient for these values.

An unrelated update (pull request #1043) disallows deposits with the address of the output token set to 0x0. Historically, in Across, setting the output token address to zero implied that it is to be interpreted as the equivalent of the input token on the destination chain. However, this legacy functionality has been deprecated in the bridging logic for other tokens (e.g., LayerZero OFT) and is being removed for SPL tokens as well.

Notes & Additional Information

Ambiguous Relay Data Format in Slow Fill

The slow fill leaf is computed from the SlowFill struct which includes relay data for the corresponding fill. The leaf is then verified for inclusion in the Merkle tree with a slow relay root that is computed off-chain. However, the way that the relay data should be provided may be unclear to the slow fill executor due to the way that data is serialized before hashing.

The hashing function uses AnchorSerialize::serialize to process the relay data before performing computation, and this function changes integer types to little-endian format in the resulting serialized bytes. This process may be unclear to the slow fill relayer when providing the data, resulting in a leaf that does not belong to the tree with the corresponding root.

Consider adding further documentation within the code to clarify the exact format that data should be provided to avoid confusion and failed slow fill attempts.

Update: Resolved in pull request #1053 at commit a90c895. The Across team stated:

Added a note to the execute_slow_relay_leaf function regarding the numerical encoding of token amounts when verifying the slow fill leaf.

Conclusion - Fix Review

Pull request #1045 addresses the type mismatch between Solana and EVM token amounts. It changes the data type of any amount used by the Solana program, that is either intended to be processed by or comes from an EVM chain, from u64 to [u8; 32] (i.e., a 32-byte array). Now, it is possible to represent any EVM token amount on the Solana side. The review team determined that this change fixes the reported issue, and no additional issues were identified. However, it is recommended to clarify with additional code comments how relay data should be provided to the SVM spoke due to the way AnchorSerialize handles endianness.

An unrelated update has also been proposed with pull request #1043. It disallows deposits with the address of the output token set to 0x0. This removes legacy functionality that interpreted zero address tokens as the equivalent of the specified input token on the destination chain.