Skip to main content

antegen_fiber_program/instructions/
create.rs

1use crate::constants::*;
2use crate::errors::AntegenFiberError;
3use crate::state::*;
4use anchor_lang::prelude::*;
5use anchor_lang::solana_program::instruction::Instruction;
6use anchor_lang::solana_program::program::invoke_signed;
7use anchor_lang::solana_program::system_instruction;
8
9/// Accounts required by the `create_fiber` instruction.
10/// Thread PDA is the signer (authority). Fiber must be pre-funded with rent lamports.
11#[derive(Accounts)]
12#[instruction(fiber_index: u8)]
13pub struct Create<'info> {
14    /// Thread PDA - signer (via invoke_signed from Thread Program)
15    pub thread: Signer<'info>,
16
17    /// CHECK: The fiber account to create — validated manually via PDA derivation
18    #[account(mut)]
19    pub fiber: UncheckedAccount<'info>,
20
21    pub system_program: Program<'info, System>,
22}
23
24pub fn create(
25    ctx: Context<Create>,
26    fiber_index: u8,
27    instruction: Instruction,
28    priority_fee: u64,
29    lookup_tables: Vec<Pubkey>,
30) -> Result<()> {
31    require!(
32        lookup_tables.len() <= MAX_LOOKUP_TABLES_PER_FIBER,
33        AntegenFiberError::LookupTablesExceedMax
34    );
35
36    let thread_key = ctx.accounts.thread.key();
37    let fiber_info = ctx.accounts.fiber.to_account_info();
38
39    if fiber_info.data_len() == 0 {
40        initialize_fiber(
41            &ctx.accounts.fiber,
42            &ctx.accounts.system_program,
43            &thread_key,
44            fiber_index,
45            &instruction,
46            priority_fee,
47            lookup_tables,
48        )
49    } else {
50        // Already initialized — update in place. Dispatch by discriminator so
51        // we never re-write a legacy fiber with a v1 shape (would corrupt the
52        // account on disk).
53        let compiled = compile_instruction(instruction)?;
54        let compiled_bytes = borsh::to_vec(&compiled)?;
55
56        let fiber_read = {
57            let data = fiber_info.try_borrow_data()?;
58            Fiber::try_deserialize(&mut &data[..])?
59        };
60
61        match fiber_read {
62            Fiber::Legacy(mut state) => {
63                require!(
64                    lookup_tables.is_empty(),
65                    AntegenFiberError::LegacyFiberLookupTablesUnsupported
66                );
67                state.thread = thread_key;
68                state.compiled_instruction = compiled_bytes;
69                state.priority_fee = priority_fee;
70                state.last_executed = 0;
71                state.exec_count = 0;
72                write_legacy(&fiber_info, &state)?;
73            }
74            Fiber::V1(mut state) => {
75                state.version = CURRENT_FIBER_VERSION;
76                state.thread = thread_key;
77                state.compiled_instruction = compiled_bytes;
78                state.priority_fee = priority_fee;
79                state.last_executed = 0;
80                state.exec_count = 0;
81                state.lookup_tables = lookup_tables;
82                write_versioned(&fiber_info, &state)?;
83            }
84        }
85
86        Ok(())
87    }
88}
89
90/// Shared helper for manual fiber account initialization.
91/// New writes always emit `FiberVersionedState` — legacy accounts are
92/// never created post-PR.
93pub fn initialize_fiber<'info>(
94    fiber: &UncheckedAccount<'info>,
95    system_program: &Program<'info, System>,
96    thread_key: &Pubkey,
97    fiber_index: u8,
98    instruction: &Instruction,
99    priority_fee: u64,
100    lookup_tables: Vec<Pubkey>,
101) -> Result<()> {
102    require!(
103        lookup_tables.len() <= MAX_LOOKUP_TABLES_PER_FIBER,
104        AntegenFiberError::LookupTablesExceedMax
105    );
106
107    let fiber_info = fiber.to_account_info();
108
109    let (expected_pda, bump) = Pubkey::find_program_address(
110        &[SEED_THREAD_FIBER, thread_key.as_ref(), &[fiber_index]],
111        &crate::ID,
112    );
113    require!(
114        expected_pda.eq(&fiber.key()),
115        AntegenFiberError::InvalidFiberPDA
116    );
117
118    let space = 8 + FiberVersionedState::INIT_SPACE;
119    let rent = Rent::get()?;
120    let min_lamports = rent.minimum_balance(space);
121    require!(
122        fiber_info.lamports().ge(&min_lamports),
123        AntegenFiberError::InsufficientRent
124    );
125
126    let seeds: &[&[u8]] = &[
127        SEED_THREAD_FIBER,
128        thread_key.as_ref(),
129        &[fiber_index],
130        &[bump],
131    ];
132
133    invoke_signed(
134        &system_instruction::allocate(&fiber.key(), space as u64),
135        &[fiber_info.clone(), system_program.to_account_info()],
136        &[seeds],
137    )?;
138
139    invoke_signed(
140        &system_instruction::assign(&fiber.key(), &crate::ID),
141        &[fiber_info.clone(), system_program.to_account_info()],
142        &[seeds],
143    )?;
144
145    let compiled = compile_instruction(instruction.clone())?;
146    let compiled_bytes = borsh::to_vec(&compiled)?;
147
148    let state = FiberVersionedState {
149        version: CURRENT_FIBER_VERSION,
150        thread: *thread_key,
151        compiled_instruction: compiled_bytes,
152        priority_fee,
153        last_executed: 0,
154        exec_count: 0,
155        lookup_tables,
156    };
157
158    write_versioned(&fiber_info, &state)
159}
160
161pub(crate) fn write_versioned(fiber_info: &AccountInfo, state: &FiberVersionedState) -> Result<()> {
162    let mut data = fiber_info.try_borrow_mut_data()?;
163    data[..8].copy_from_slice(FiberVersionedState::DISCRIMINATOR);
164    let state_bytes = borsh::to_vec(state)?;
165    data[8..8 + state_bytes.len()].copy_from_slice(&state_bytes);
166    Ok(())
167}
168
169pub(crate) fn write_legacy(fiber_info: &AccountInfo, state: &FiberState) -> Result<()> {
170    let mut data = fiber_info.try_borrow_mut_data()?;
171    data[..8].copy_from_slice(FiberState::DISCRIMINATOR);
172    let state_bytes = borsh::to_vec(state)?;
173    data[8..8 + state_bytes.len()].copy_from_slice(&state_bytes);
174    Ok(())
175}