Assessment reports>Hyperlane - Radix>High findings>Warp Payload doesn't support custom divisibility
Category: Coding Mistakes

Warp Payload doesn't support custom divisibility

High Impact
High Severity
Medium Likelihood

Description

When consuming a WarpPayload message, the amount field is treated as a Decimal type.

pub struct WarpPayload {
    /// 32-byte Address in destination convention
    pub recipient: Bytes32,
    /// 32-byte Amount
    pub amount: Decimal,
}

impl From<&RawWarpPayload> for WarpPayload {
    fn from(m: &RawWarpPayload) -> Self {
        let recipient: Bytes32 = m[0..32].into();

        // Next 32 bytes encode the amount
        // In the future it might be possible that the warp payload carries additional metadata
        let mut b = m[32..64].to_vec();
        b.reverse();
        let amount = U256::from_le_bytes(b.as_ref());

        let amount = I192::try_from(amount).expect("Invalid payload");
        let amount = Decimal::from_attos(amount);

        Self { recipient, amount }
    }
}

The incoming amount is 32 bytes or 256 bits, but the Decimal type on the other hand, is just valid for values up to 192 bits. This is because the Radix Decimal uses Attos, or units of size . The valid range for a Decimal m is -2^(192 - 1) <= m < 2^(192 - 1), meaning values above this amount will crash. Additionally, the built-in constants like Decimal::one() is actually equal to 1000000000000000000 in a WarpPayload.

More importantly, this code does not handle custom divisibility and incorrectly assumes that the token always uses 18 decimals.

Impact

If another chain uses custom decimals for their divisibility, this could lead to the payload being discarded or being parsed incorrectly. When it is discarded due to parsing errors, there are no custom errors displaying what went wrong. If the WarpRoute uses custom divisibility, this leads to values being scaled incorrectly.

Example: If someone sends 1,000,000 subunits of a token that uses divisibility 6, this turns into "0.000000000001" and will not correctly be parsed into the ResourceManager, which would have its own divisibility configured.

In general, it blocks everyone from setting up a WarpRoute with custom divisibility at all. Only 18 decimals will be supported.

Recommendations

Scale the amount by the proper divisibility and throw a custom error when a chain with custom divisibility tries to send an incompatible amount. Properly document the range of the incoming amount. Do not use Decimal for the amount, as it does not take the underlying divisibility into account. Instead scale it at the ingress.

Remediation

For the crash issue, Hyperlane states:

That the Radix implementation only supports 24 bytes (instead of the 32) is totally fine and must be respected by the WarpRoute owner.

For the divisibility issue:

This issue has been acknowledged by Hyperlane, and a fix was implemented in commit 1cc52db8.

Zellic © 2025Back to top ↑