Repository navigation
fix(stablecoin): order guest instructions to match the enum - #421
Closed
gravityblast wants to merge 12 commits into
Closed
gravityblast wants to merge 12 commits into
gravityblast wants to merge 12 commits into
Conversation
Point every crate at logos-execution-zone v0.2.5-rc2 and at the matching spel framework revision. The pin has to move everywhere at once: lee_core types cross every layer -- the program crates, their *_core crates, the guests, the host method crates and the FFI modules -- so there is no partial bump that builds. The commits that follow port one program each onto the new interfaces, and only the last one leaves the workspace green.
Every entry point returns Vec<AccountStateDiff> instead of Vec<AccountPostState>. A diff carries a change rather than a whole account -- a BalanceDiff plus post_data, normalized to None when the data is untouched -- and ownership is not part of it at all: the runtime derives the post owner itself. ProgramId is gone as a distinct type, since a program is now addressed by its deployed header account, so ctx.self_program_id became ctx.self_account_id. The program still needs it to check that a definition account is owned by us; it no longer needs it to say who should own an account afterwards. Claim went with it. `new_claimed_if_default(post, Claim::Authorized)` in transfer and mint is a plain diff now: v0.2.5 acquires ownership implicitly on the first data write to a default-owned account, and there is no authorization variant to ask for. Both sites carry a comment on why no is_authorized assert replaces the old claim -- crediting an uninitialized recipient is a supported flow, since the private foreign-init path knows the recipient only by npk/vpk, and a guard here cannot tell that apart from an unauthorized public recipient. token_core::Instruction gains Borsh derives: v0.2.5 decodes instruction data as Borsh rather than through RISC Zero's serde codec. serde stays for tooling and the IDL. Borsh tags variants positionally, so the enum is append-only from here -- noted on the type. StateDiffExt is a #[cfg(test)] extension trait reconstructing the resulting account from a diff, delegating to lee_core's own post_state, so the existing assertions keep reading in terms of post-state accounts. artifacts/token-idl.json picks up a NewTokenDefinition type definition that the regenerated IDL now emits.
Same shape as the token port: handlers return Vec<AccountStateDiff>, ctx.self_program_id became ctx.self_account_id, Instruction gains Borsh derives for the new instruction wire format, and a #[cfg(test)] StateDiffExt reconstructs post-state accounts so the existing assertions keep reading the way they did. PDA derivation moves off ProgramId: compute_price_observations_pda, compute_oracle_price_account_pda and compute_current_tick_account_pda take the oracle's AccountId now, since a program is addressed by its deployed ProgramHeader account. The *_pda_seed helpers are unchanged and still feed AccountId::for_public_pda; they are just no longer needed at the call sites. Claim::Pda is gone, and with it the runtime check that the claimed address matched the seed the program presented. All three creating handlers already assert their account id against the same derivation before writing, so that assert is now the only thing binding these accounts to their PDAs -- worth knowing before touching them. Writing the data is the claim: v0.2.5 makes the program that writes to a default-owned account its owner. examples/twap_oracle_pdas.rs takes a base58 account id for the oracle instead of a ProgramId, dropping the limb/hex/base58 ImageID parsing that existed only to reach the old type.
Handlers return Vec<AccountStateDiff>, ctx.self_program_id became ctx.self_account_id, and Instruction gains Borsh derives for the new instruction wire format. Unlike token and twap_oracle, the ATA program takes another program's id as an explicit instruction argument, so this changes the caller-facing interface: Create, Transfer and Burn all carry token_program_id, now an AccountId rather than a ProgramId. artifacts/ata-idl.json retypes those three args from "program_id" to "account_id". Anything building ATA instructions -- scripts, the FFI modules, wallet invocations -- has to pass the new form. compute_ata_seed hashes token_program_id.to_bytes() instead of expanding eight u32 limbs little-endian by hand. Those are the same 32 bytes, so a given token program id yields the same ATA address it did before; what changes an ATA address is the token program itself being deployed at a different id. get_associated_token_account_id and verify_ata_and_get_seed take AccountId for both the token and ATA program ids, for the same reason.
…h holdings faucet_mint returns Vec<AccountStateDiff>, and its chained Token::MintWithAuthority names accounts by id -- so the account ids are captured before the pre-states are moved into the diffs. Instruction gains Borsh derives, and ctx.self_program_id became ctx.self_account_id. verify_mint_allowance_and_get_seed is now called only for its address assertion; the seed it returns is unused, because ownership of the allowance PDA comes from writing its data rather than from presenting a claim. The faucet's split -- permissive for existing holdings, consent required for fresh ones -- used to be enforced downstream rather than here. v0.2.4's token program claimed a fresh holding via Claim::Authorized, which failed unless the recipient had authorized it, so faucet_mint deliberately did not check user_holding.is_authorized itself. v0.2.5 removed claims: acquire_ownership_on_data_write fires on any data write to a default-owned account with no authorization check. Without a local guard the chained mint would materialize a holding at any unowned address the caller names, making it token-program-owned permanently and denying it to every other program -- and the 24h cooldown does not contain that, since it keys on (recipient, definition) and a fresh keypair resets it. So the rule is now enforced where it is relied upon: an uninitialized user_holding must carry is_authorized. Topping up an existing holding is unchanged and still open to anyone. Covered by rejects_creating_a_holding_the_caller_cannot_sign_for.
Handlers return Vec<AccountStateDiff>, Instruction gains Borsh derives, and ctx.self_program_id became ctx.self_account_id. The IDL retypes token_program_id and twap_oracle_program_id on Initialize and AmmConfig from "program_id" to "account_id" -- caller-facing, like ata. Chained calls now name accounts by id. ChainedCall carries pre_state_ids rather than whole pre-states, and the runtime resolves each id against the transaction's accumulated diff before falling back to global state. That deletes every hand-built synthetic post-state this program used to construct to show a callee an account it had just written: post_init_vault, pool_lp_after_lock, pool_price_source, and swap's hand-recomputed second-touch vault balance. The callee now sees the real applied diff, so these cannot drift from what the program actually wrote. Authority over a chained call's accounts moves to pda_seeds: with_pda_seeds(...) feeds compute_public_authorized_pdas, which sets is_authorized for the callee. There is no pre-state flag to set by hand any more -- presenting the seed is the authority.
Handlers return Vec<AccountStateDiff>, Instruction gains Borsh derives, and ctx.self_program_id became ctx.self_account_id. Every ProgramId parameter -- stablecoin_program_id and the token/oracle ids threaded through the core modules -- becomes an AccountId, including the [Nu32; 8] test fixtures in protocol_parameters, redemption_price_state and stability_fee_accumulator, which are now AccountId::new([Nu8; 32]). Chained calls name accounts by id, so open_position, withdraw_collateral and initialize_program capture the ids they need before the pre-states are moved into the diffs, and carry authority as pda_seeds: the position vault seed for the chained InitializeAccount, and the stablecoin definition plus master holding seeds for the initialize_program chain. Claim::Pda is gone from every PDA-creating handler. verify_position_and_get_seed is now called for its address assertion alone -- its seed is no longer carried anywhere, because writing the position data is itself the ownership claim. verify_position_vault_and_get_seed still returns a seed that is used, but as the chained call's authority rather than as a claim. open_position keeps its user_holding.is_authorized assert, which it always had: unlike the faucet, this program never delegated that check downstream.
Deployment goes through with_programs instead of ProgramDeploymentTransaction, which v0.2.5 deleted. The five deploy_* helpers seed each program in the shape the program_loader flow produces -- a loader-owned header plus its segment -- so call sites are unchanged. Program ids become AccountId::from(<ELF>_ID): the harness seeds the header at the image-id bijection address, which is where a program lives here. The privacy helpers follow the collapsed witness API. The three InputAccountIdentity::Private* variants became one Private(PrivateWitness), so private_authorized_init_identity and private_authorized_update_identity now take an AuthorizationSecretKey rather than a nullifier secret key, deriving the nsk from it; GroupOwner::admit_member returns that key. private_foreign_init_identity keeps its signature but now pairs with is_authorized: false, because the circuit asserts a Regular witness's authorization matches whether a credential was supplied. Transactions carry instruction data as Borsh, and chained calls name accounts by id, so the hand-built pre-states the old tests passed to callees are gone.
Two changes the release forces, entangled across the same files, and the spel bump that carries them: 1ba0b6b7 moves spel-cli to Borsh encoding and drops the serde derives from the Instruction enum spel-framework-macros generates. This is the repo-side half of that flip. Program addressing: lee_core::program::ProgramId -- the [u32; 8] ImageID -- is gone. A program now lives at the account its `program_loader` header was deployed to, so every ProgramId in the module FFIs becomes an AccountId. The wire format is unchanged (callers still pass 64 hex characters), and a program deployed at the image-id bijection address keeps the id it had. Instruction encoding: the wire format is Borsh, but the FFI plan ops still built their instructions with risc0_zkvm::serde::to_vec, which kept compiling and kept producing bytes the runtime rejects; every encode site moves to borsh::to_vec. Their C++ consumers then decoded each element of the resulting byte array as a u32 word and emitted four little-endian bytes, quadrupling every instruction -- so the guest's Borsh decode failed, the program panicked, and the transaction was charged its full gas budget, bumped the nonce, wrote nothing, and was never logged. All three module decoders now pass bytes through, and their unit tests assert that contract instead of the old word expansion. The programs' Instruction enums drop their serde derives to match, so this cannot recur: with Borsh the only encoding they implement, reaching for a serde encoder is a compile error rather than silently wrong bytes.
Until v0.2.5 a program was addressed by its ImageID, so the module could derive its own address from the binary at AMM_PROGRAM_BIN. v0.2.5 addresses a program by the account its `program_loader` header was deployed to, chosen at deploy time and not recoverable from the ELF, so that fallback now yields a well-formed id that simply is not where the program lives -- and every PDA derived from it is wrong too. Read the deployed id from AMM_PROGRAM_ID instead, and drop the binary fallback along with loadAmmElf() and toHex(). An app-selected id (setAmmProgramId, i.e. the registry) still takes precedence; AMM_PROGRAM_ID is the fallback for local and headless use. The UI gains a config_read_failed string, and no_program_bin is reworded for the new source. RegistryLoader also warns when AMM_REGISTRY_URL is set but ignored because a local token or pool list takes precedence, which otherwise looks like the registry silently failing to load.
The end-to-end setup needed several things the release changed:
- Account mentions must be qualified (Public/<id>); a bare base58 id falls
through to the label branch and the deploy fails.
- Every spel submission needs --fee-payer, or the sequencer answers
"Incorrect fee".
- The accounts that sign from the UI need a native balance of their own,
because the modules submit with the first signing account as payer. A new
funding step tops up the six of them from the deploy payer, idempotently.
- run_tx now accepts the wallet's confirmation marker ("Transaction is
included in block N") as well as spel's, so funding transfers stopped
being reported as unconfirmed; deploys and funding are strict again.
- The faucet manifest carries feePayer and fundAmount so faucet-swap.mjs can
do the same without hardcoding them.
Diagnostics move to stderr so a function can return a value on stdout without
its progress output being captured with it, and transaction-data dumps are
suppressed unless SHOW_TX_DATA=1. The recovery note is corrected: segments are
write-once and headers deployed --immutable cannot be reclaimed, so
FORCE_BOOTSTRAP does not help -- only a chain reset does.
faucet-swap.mjs funds holder2's holding itself, between initialize_account and
the swap: setup cannot, because the holding does not exist until the test
creates it, and initialize_account asserts its target is still
Account::default() -- balance and nonce zero -- so the funding has to land
after it.
spel encodes the IDL position as the Borsh tag, so the IDL must follow stablecoin_core::Instruction order.
0x-r4bbit
force-pushed
the
refactor/lez-v0.2.5
branch
4 times, most recently
from
October 20, 2026 09:38
05282f1 to
38bd3c9
Compare
Collaborator
|
This will be fixed via SPEL in #385 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Reorders the stablecoin
#[instruction]functions to matchstablecoin_core::Instructionand regenerates the IDL.spel uses the IDL position as the Borsh tag (logos-co/spel#288), so from index 8 on, spel calls decoded as the wrong variant (e.g.
set-controller-gains→SetAdmin). Same fix #385 applied to token.