From e03164baf33b6638d16580b1c6fcd99adb887b67 Mon Sep 17 00:00:00 2001 From: Andrea Franz Date: Thu, 23 Jul 2026 15:36:11 +0200 Subject: [PATCH] =?UTF-8?q?fix(amm):=20LE-serialize=20instruction=20words;?= =?UTF-8?q?=20correct=20ELF=E2=86=92ProgramBinary=20docs;=20clarify=20shar?= =?UTF-8?q?edWalletIsOpen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/amm/src/AmmUiBackend.cpp | 17 +++++++++++++++-- apps/amm/src/AmmUiBackend.h | 5 +++-- apps/amm/src/AmmUiBackend.rep | 3 ++- programs/amm/client-ffi/src/lib.rs | 6 ++++-- programs/amm/client-ffi/src/pda.rs | 4 +++- 5 files changed, 27 insertions(+), 8 deletions(-) diff --git a/apps/amm/src/AmmUiBackend.cpp b/apps/amm/src/AmmUiBackend.cpp index 10fe05f..f9eb89d 100644 --- a/apps/amm/src/AmmUiBackend.cpp +++ b/apps/amm/src/AmmUiBackend.cpp @@ -285,6 +285,9 @@ bool AmmUiBackend::sharedWalletIsOpen() // the real on-disk wallet, so the user saw no accounts. Keying off accounts // alone means a genuinely-open shared wallet (Basecamp) is still adopted, // while a closed standalone core correctly falls through to open-from-disk. + // Tradeoff: a shared wallet that is open but holds ZERO accounts is treated + // as closed here, so it won't be adopted — an accepted edge, preferable to + // the default-sequencer false-positive that keying off the address caused. return !QJsonArray::fromVariantList(m_logos->logos_execution_zone.list_accounts()).isEmpty(); } @@ -783,8 +786,18 @@ QString AmmUiBackend::swapExactInput(QString defAHex, QString defBHex, QString u // this file) plus the program's id as hex, not the raw ELF: the program is // already deployed and referenced by id. There is no program_dependencies // arg — the sequencer resolves the AMM's chained token/twap calls on-chain. - const QByteArray instructionBytes(reinterpret_cast(instruction.data()), - static_cast(instruction.size() * sizeof(uint32_t))); + // Serialize the u32 instruction words to bytes explicitly as little-endian, + // matching the protocol's LE-u32 wire format. A reinterpret_cast of the + // in-memory vector would be host-endian-dependent and byte-swap on a + // big-endian host, making the guest decode a different instruction. + QByteArray instructionBytes; + instructionBytes.reserve(static_cast(instruction.size() * sizeof(uint32_t))); + for (const uint32_t w : instruction) { + instructionBytes.append(static_cast(w & 0xFF)); + instructionBytes.append(static_cast((w >> 8) & 0xFF)); + instructionBytes.append(static_cast((w >> 16) & 0xFF)); + instructionBytes.append(static_cast((w >> 24) & 0xFF)); + } // Derive the deployed AMM program id from the ELF and hex-encode it as the // canonical 32-byte hex (little-endian per u32 word — matches `spel diff --git a/apps/amm/src/AmmUiBackend.h b/apps/amm/src/AmmUiBackend.h index 50feff6..0fd09f0 100644 --- a/apps/amm/src/AmmUiBackend.h +++ b/apps/amm/src/AmmUiBackend.h @@ -81,8 +81,9 @@ private: void refreshSequencerAddr(); void saveWallet(); - // Returns the deployed AMM program ELF bytes from $AMM_PROGRAM_BIN, or an empty - // QByteArray (with a qWarning) if the env var is unset/unreadable/empty. + // Returns the deployed AMM program-binary bytes (a RISC Zero ProgramBinary + // .bin, not a raw ELF) from $AMM_PROGRAM_BIN, or an empty QByteArray (with a + // qWarning) if the env var is unset/unreadable/empty. QByteArray loadAmmElf(); // Probe the configured sequencer over HTTP and update sequencerReachable. diff --git a/apps/amm/src/AmmUiBackend.rep b/apps/amm/src/AmmUiBackend.rep index 055e610..4827e07 100644 --- a/apps/amm/src/AmmUiBackend.rep +++ b/apps/amm/src/AmmUiBackend.rep @@ -46,7 +46,8 @@ class AmmUiBackend // AMM // Derives the AMM pool's PDAs (config/pool/vaults/current-tick) from the - // deployed AMM program's ELF (see AMM_PROGRAM_BIN) and reads the pool's + // deployed AMM program binary (see AMM_PROGRAM_BIN — a RISC Zero + // ProgramBinary .bin, not a raw ELF) and reads the pool's // on-chain reserves. Returns `{ exists: false }` if the AMM program bin // isn't configured, the AMM isn't initialized, or the pool has no // liquidity yet. diff --git a/programs/amm/client-ffi/src/lib.rs b/programs/amm/client-ffi/src/lib.rs index b27aa5e..c9ff442 100644 --- a/programs/amm/client-ffi/src/lib.rs +++ b/programs/amm/client-ffi/src/lib.rs @@ -106,8 +106,10 @@ pub unsafe extern "C" fn amm_client_free_words(w: AmmWords) { } } -/// Computes the `ProgramId` (Image ID) of a compiled guest ELF. Returns -/// `false` on an invalid ELF, leaving `out` unwritten. +/// Computes the `ProgramId` (Image ID) of a deployed program binary — the RISC +/// Zero `ProgramBinary` (`.bin`) format, NOT a raw ELF (the `elf` bytes are +/// decoded via `ProgramBinary::decode`). Returns `false` on invalid input, +/// leaving `out` unwritten. /// /// # Safety /// `elf` must be valid for reads of `elf_len` bytes, and `out` must be a diff --git a/programs/amm/client-ffi/src/pda.rs b/programs/amm/client-ffi/src/pda.rs index 1bf3c3c..a1bb093 100644 --- a/programs/amm/client-ffi/src/pda.rs +++ b/programs/amm/client-ffi/src/pda.rs @@ -6,7 +6,9 @@ use nssa_core::{account::AccountId, program::ProgramId}; use risc0_binfmt::ProgramBinary; use twap_oracle_core::compute_current_tick_account_pda; -/// Computes the `ProgramId` (Image ID) of a compiled guest ELF, the same way +/// Computes the `ProgramId` (Image ID) of a deployed program binary — the RISC +/// Zero `ProgramBinary` (`.bin`) format produced by the guest build, NOT a raw +/// ELF (the `elf` bytes are decoded via `ProgramBinary::decode`) — the same way /// the sequencer/wallet does when deploying a program. pub fn program_id_from_elf(elf: &[u8]) -> Result { let binary = ProgramBinary::decode(elf).map_err(|e| format!("{e:?}"))?;