2026-05-06 17:08:15 -03:00
|
|
|
#![cfg_attr(not(test), no_main)]
|
2026-03-17 18:08:53 +01:00
|
|
|
|
|
|
|
|
use spel_framework::prelude::*;
|
2026-05-04 10:34:10 -03:00
|
|
|
use spel_framework::context::ProgramContext;
|
2026-05-13 17:24:11 -03:00
|
|
|
use nssa_core::{account::AccountWithMetadata, program::ProgramId};
|
2026-03-17 18:08:53 +01:00
|
|
|
|
2026-05-06 17:08:15 -03:00
|
|
|
#[cfg(not(test))]
|
2026-03-17 18:08:53 +01:00
|
|
|
risc0_zkvm::guest::entry!(main);
|
|
|
|
|
|
|
|
|
|
#[lez_program(instruction = "ata_core::Instruction")]
|
|
|
|
|
mod ata {
|
2026-05-06 17:08:15 -03:00
|
|
|
#[expect(
|
|
|
|
|
unused_imports,
|
|
|
|
|
reason = "SPEL instruction macro requires importing parent-scope handler types"
|
|
|
|
|
)]
|
2026-03-17 18:08:53 +01:00
|
|
|
use super::*;
|
|
|
|
|
|
2026-05-13 17:24:11 -03:00
|
|
|
/// Create the Associated Token Account for (token program, owner, definition).
|
2026-03-17 18:08:53 +01:00
|
|
|
/// Idempotent: no-op if the account already exists.
|
2026-05-13 17:24:11 -03:00
|
|
|
/// The token program is selected explicitly by `token_program_id`; the token definition and
|
|
|
|
|
/// any existing ATA occupant must be owned by that program.
|
2026-03-17 18:08:53 +01:00
|
|
|
#[instruction]
|
|
|
|
|
pub fn create(
|
2026-05-04 10:34:10 -03:00
|
|
|
ctx: ProgramContext,
|
2026-06-26 16:55:23 -03:00
|
|
|
#[account(mut)]
|
2026-03-17 18:08:53 +01:00
|
|
|
owner: AccountWithMetadata,
|
|
|
|
|
token_definition: AccountWithMetadata,
|
2026-06-26 16:55:23 -03:00
|
|
|
#[account(mut)]
|
2026-03-17 18:08:53 +01:00
|
|
|
ata_account: AccountWithMetadata,
|
2026-05-13 17:24:11 -03:00
|
|
|
token_program_id: ProgramId,
|
2026-03-17 18:08:53 +01:00
|
|
|
) -> SpelResult {
|
|
|
|
|
let (post_states, chained_calls) = ata_program::create::create_associated_token_account(
|
|
|
|
|
owner,
|
|
|
|
|
token_definition,
|
|
|
|
|
ata_account,
|
2026-05-04 10:34:10 -03:00
|
|
|
ctx.self_program_id,
|
2026-05-13 17:24:11 -03:00
|
|
|
token_program_id,
|
2026-03-17 18:08:53 +01:00
|
|
|
);
|
2026-05-12 11:33:19 +02:00
|
|
|
Ok(spel_framework::SpelOutput::execute(post_states, chained_calls))
|
2026-03-17 18:08:53 +01:00
|
|
|
}
|
|
|
|
|
|
2026-04-15 14:55:04 -03:00
|
|
|
/// Transfer tokens FROM owner's ATA to a recipient token holding account.
|
2026-05-13 17:24:11 -03:00
|
|
|
/// The token program is selected explicitly by `token_program_id`; the sender ATA and recipient
|
|
|
|
|
/// holding must be owned by that program.
|
fix(ata): lock down `ATA::Transfer` recipient contract
Enforce at the ATA layer that the recipient token holding is already
initialized, owned by the same token program as the sender ATA, decodes
to a valid `TokenHolding`, and points at the same token definition as
the sender. Align the core instruction doc and guest wrapper doc with
that contract, and cover the boundary with unit tests (default,
foreign-owned, malformed, mismatched-definition recipients, plus the
missing-owner-auth and happy paths) and end-to-end integration tests
(default and mismatched-definition recipients).
Without this, the downstream `token::Transfer` default-recipient
`Claim::Authorized` path was reachable through ATA, so integrators had
to reverse-engineer recipient semantics from token/runtime internals.
2026-05-11 12:44:46 -03:00
|
|
|
/// The recipient holding must already be initialized, be owned by the same token program
|
|
|
|
|
/// as the sender ATA, and point at the same token definition as the sender.
|
2026-03-17 18:08:53 +01:00
|
|
|
#[instruction]
|
|
|
|
|
pub fn transfer(
|
2026-05-04 10:34:10 -03:00
|
|
|
ctx: ProgramContext,
|
2026-06-26 16:55:23 -03:00
|
|
|
#[account(signer)]
|
2026-03-17 18:08:53 +01:00
|
|
|
owner: AccountWithMetadata,
|
2026-06-26 16:55:23 -03:00
|
|
|
#[account(mut)]
|
2026-03-17 18:08:53 +01:00
|
|
|
sender_ata: AccountWithMetadata,
|
2026-06-26 16:55:23 -03:00
|
|
|
#[account(mut)]
|
2026-03-17 18:08:53 +01:00
|
|
|
recipient: AccountWithMetadata,
|
2026-05-13 17:24:11 -03:00
|
|
|
token_program_id: ProgramId,
|
2026-03-17 18:08:53 +01:00
|
|
|
amount: u128,
|
|
|
|
|
) -> SpelResult {
|
|
|
|
|
let (post_states, chained_calls) =
|
|
|
|
|
ata_program::transfer::transfer_from_associated_token_account(
|
|
|
|
|
owner,
|
|
|
|
|
sender_ata,
|
|
|
|
|
recipient,
|
2026-05-04 10:34:10 -03:00
|
|
|
ctx.self_program_id,
|
2026-05-13 17:24:11 -03:00
|
|
|
token_program_id,
|
2026-03-17 18:08:53 +01:00
|
|
|
amount,
|
|
|
|
|
);
|
2026-05-12 11:33:19 +02:00
|
|
|
Ok(spel_framework::SpelOutput::execute(post_states, chained_calls))
|
2026-03-17 18:08:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Burn tokens FROM owner's ATA.
|
2026-05-13 17:24:11 -03:00
|
|
|
/// The token program is selected explicitly by `token_program_id`; the holder ATA and token
|
|
|
|
|
/// definition must be owned by that program.
|
2026-03-17 18:08:53 +01:00
|
|
|
#[instruction]
|
|
|
|
|
pub fn burn(
|
2026-05-04 10:34:10 -03:00
|
|
|
ctx: ProgramContext,
|
2026-06-26 16:55:23 -03:00
|
|
|
#[account(signer)]
|
2026-03-17 18:08:53 +01:00
|
|
|
owner: AccountWithMetadata,
|
2026-06-26 16:55:23 -03:00
|
|
|
#[account(mut)]
|
2026-03-17 18:08:53 +01:00
|
|
|
holder_ata: AccountWithMetadata,
|
2026-06-26 16:55:23 -03:00
|
|
|
#[account(mut)]
|
2026-03-17 18:08:53 +01:00
|
|
|
token_definition: AccountWithMetadata,
|
2026-05-13 17:24:11 -03:00
|
|
|
token_program_id: ProgramId,
|
2026-03-17 18:08:53 +01:00
|
|
|
amount: u128,
|
|
|
|
|
) -> SpelResult {
|
|
|
|
|
let (post_states, chained_calls) =
|
|
|
|
|
ata_program::burn::burn_from_associated_token_account(
|
|
|
|
|
owner,
|
|
|
|
|
holder_ata,
|
|
|
|
|
token_definition,
|
2026-05-04 10:34:10 -03:00
|
|
|
ctx.self_program_id,
|
2026-05-13 17:24:11 -03:00
|
|
|
token_program_id,
|
2026-03-17 18:08:53 +01:00
|
|
|
amount,
|
|
|
|
|
);
|
2026-05-12 11:33:19 +02:00
|
|
|
Ok(spel_framework::SpelOutput::execute(post_states, chained_calls))
|
2026-03-17 18:08:53 +01:00
|
|
|
}
|
|
|
|
|
}
|