anchor icon indicating copy to clipboard operation
anchor copied to clipboard

LLVM and Rust u128 alignment update breaks `#[account(zero_copy)]` with u128

Open JakkuSakura opened this issue 1 year ago • 5 comments

Long story short. The previous versions of rust and llvm treat u128 as 8 alignments incorrectly on x86_64. so does #[Account(zero_copy)]. A recent update fixed this issue, but breaks all existing #[account(zero_copy)] with u128

I'm surprised that nobody mentioned this issue here. So I'm just links related issues. https://blog.rust-lang.org/2024/03/30/i128-layout-update.html https://github.com/drift-labs/protocol-v2/issues/891 https://github.com/rust-lang/rust/issues/54341

A workaround would be defining a struct u128([u64; 2]) and replace all primitive type u128, inspired by https://solana.stackexchange.com/questions/7720/using-u128-without-sacrificing-alignment-8

JakkuSakura avatar Jul 24 '24 15:07 JakkuSakura

Thanks for creating the issue but Anchor itself doesn't have any alignment assumptions when using #[account(zero_copy)] (internal bytemuck does).

Solana usually keeps layouts consistent — it might be worth creating an issue in https://github.com/anza-xyz/llvm-project.

acheroncrypto avatar Jul 24 '24 15:07 acheroncrypto

The issue isn't that the Solana layout changed -- it's that it didn't change while everyone else's did.

If you are trying to deserialize #[zero_copy(unsafe)] anchor data containing a u128 or i128 with a Rust 1.77+ program, it won't work because the data was serialized using an old Rust layout that's no longer compatible.

Anchor itself doesn't have any alignment assumptions when using #[account(zero_copy)] (internal bytemuck does).

Older Anchor programs (and new ones using #[zero_copy(unsafe)]) are not defined as repr(C), so by default they use repr(Rust). That is the assumption causing the issue here.

While technically bytemuck is doing the heavy lifting its anchor that adds the repr. And as per the bytemuck docs:

The type needs to be repr(C) or repr(transparent).

Anchor fixed this issue in 0.20 but a lot of existing programs cannot change their representations. There are a lot of important programs using #[zero_copy(unsafe)] (and thus repr(Rust)) because they were deployed before Anchor 0.20 was released.

matt-allan avatar Aug 01 '24 19:08 matt-allan

I pushed a self-contained reproduction here: https://github.com/matt-allan/anchor-layout-issue

In this case the issue between 1.77 and stable is not the change in alignment but that the order of the fields changed.

On 1.77 I get the expected value for the first field:

TickArray {
    start_tick_index: -1232,

The offset is 0, which is expected:

Offset of start_tick_index in TickArray: 0
Alignment of TickArray: 1
Alignment of Tick: 1
Offset of liquidity_gross in Tick: 17

On stable I don't get the expected value:

TickArray {
    start_tick_index: 0,

The offset of the field has changed:

Offset of start_tick_index in TickArray: 9944
Alignment of TickArray: 1
Alignment of Tick: 1
Offset of liquidity_gross in Tick: 17

9944 is the size of Tick (113) x the # of array elements (88), so it's moved.

matt-allan avatar Aug 01 '24 20:08 matt-allan

Still getting this, downgrading to 1.76 only fixes it on some platforms (x86_64-pc-windows-msvc but not aarch64/m1) - would a compatability layer be an easy fix?

AstroOrbis avatar Jan 25 '25 23:01 AstroOrbis

This is still a problem in local cargo testing and local testing with test-bpf, you will occasionally see a struct that thinks it should be aligned 16 due to u128 usage and will fail on bytemuck::from_bytes with the general panic TargetAlignmentGreaterAndInputNotAligned.

The workaround is to use bytemuck::try_pod_read_unaligned, possibly just locally with a feature flag, but that's not ideal.

jgur-psyops avatar Apr 18 '25 22:04 jgur-psyops

Sounds like a solana issue

Henry-E avatar Aug 04 '25 11:08 Henry-E

Someone should probably check if this is solana specific or anchor specific and if there's a potential solution.

Henry-E avatar Aug 05 '25 10:08 Henry-E