solana_fund/
entrypoint.rs

1//! Fund entrypoint.
2
3#![cfg(not(feature = "no-entrypoint"))]
4
5solana_security_txt::security_txt! {
6    name: "Solana Farms",
7    project_url: "https://github.com/solana-labs/solana-program-library/tree/master/farms",
8    contacts: "email:solana.farms@protonmail.com",
9    policy: "",
10    preferred_languages: "en",
11    auditors: "Halborn"
12}
13
14use {
15    crate::{
16        fund_info::FundInfo,
17        instructions::{
18            add_custody::add_custody, add_vault::add_vault, approve_deposit::approve_deposit,
19            approve_withdrawal::approve_withdrawal, cancel_deposit::cancel_deposit,
20            cancel_withdrawal::cancel_withdrawal, deny_deposit::deny_deposit,
21            deny_withdrawal::deny_withdrawal, disable_deposits::disable_deposits,
22            disable_withdrawals::disable_withdrawals, init::init, lock_assets::lock_assets, orca,
23            raydium, remove_custody::remove_custody, remove_multisig::remove_multisig,
24            remove_vault::remove_vault, request_deposit::request_deposit,
25            request_withdrawal::request_withdrawal, set_admin_signers::set_admin_signers,
26            set_assets_tracking_config::set_assets_tracking_config,
27            set_deposit_schedule::set_deposit_schedule,
28            set_withdrawal_schedule::set_withdrawal_schedule, start_liquidation::start_liquidation,
29            stop_liquidation::stop_liquidation, unlock_assets::unlock_assets,
30            update_assets_with_custody::update_assets_with_custody,
31            update_assets_with_vault::update_assets_with_vault, user_init::user_init,
32            withdraw_fees::withdraw_fees,
33        },
34    },
35    solana_farm_sdk::{
36        error::FarmError,
37        fund::Fund,
38        id::{main_router, main_router_admin, main_router_multisig},
39        instruction::{amm::AmmInstruction, fund::FundInstruction, vault::VaultInstruction},
40        log::sol_log_params_short,
41        program::{account, multisig},
42        refdb,
43        string::ArrayString64,
44    },
45    solana_program::{
46        account_info::{next_account_info, AccountInfo},
47        entrypoint,
48        entrypoint::ProgramResult,
49        log::sol_log_compute_units,
50        msg,
51        program_error::ProgramError,
52        pubkey::Pubkey,
53    },
54};
55
56fn log_start(instruction: &str, fund_name: &ArrayString64) {
57    msg!(
58        "Processing FundInstruction::{} for {}",
59        instruction,
60        fund_name.as_str()
61    );
62    sol_log_compute_units();
63}
64
65fn log_end(fund_name: &ArrayString64) {
66    sol_log_compute_units();
67    msg!("Fund {} end of instruction", fund_name.as_str());
68}
69
70fn check_admin_authority(
71    accounts: &[AccountInfo],
72    instruction_data: &[u8],
73    fund: &Fund,
74) -> Result<bool, ProgramError> {
75    let account_info_iter = &mut accounts.iter();
76    let admin_account = next_account_info(account_info_iter)?;
77    let _fund_metadata = next_account_info(account_info_iter)?;
78    let _fund_info_account = next_account_info(account_info_iter)?;
79    let multisig_account = next_account_info(account_info_iter)?;
80
81    if multisig_account.key != &fund.multisig_account
82        && multisig_account.key != &main_router_multisig::id()
83    {
84        msg!("Error: Invalid multisig account");
85        return Err(FarmError::IncorrectAccountAddress.into());
86    }
87
88    let signatures_left = multisig::sign_multisig(
89        multisig_account,
90        admin_account,
91        &main_router_admin::id(),
92        &accounts[1..],
93        instruction_data,
94    )?;
95    if signatures_left > 0 {
96        msg!(
97            "Instruction has been signed but more signatures are required: {}",
98            signatures_left
99        );
100        return Ok(false);
101    }
102
103    Ok(true)
104}
105
106fn check_manager_authority(user_account: &AccountInfo, fund: &Fund) -> ProgramResult {
107    if user_account.key != &fund.fund_manager {
108        msg!(
109            "Error: Instruction must be performed by the fund manager {}",
110            fund.fund_manager
111        );
112        Err(ProgramError::IllegalOwner)
113    } else if !user_account.is_signer {
114        Err(ProgramError::MissingRequiredSignature)
115    } else {
116        Ok(())
117    }
118}
119
120fn check_manager_authority_or_admin(
121    user_account: &AccountInfo,
122    multisig_account: &AccountInfo,
123    fund: &Fund,
124) -> ProgramResult {
125    if user_account.key != &fund.fund_manager
126        && !multisig::is_signer(multisig_account, &main_router_admin::id(), user_account.key)?
127    {
128        msg!("Error: Instruction must be performed by the fund manager or one of admin signers",);
129        Err(ProgramError::IllegalOwner)
130    } else if !user_account.is_signer {
131        Err(ProgramError::MissingRequiredSignature)
132    } else {
133        Ok(())
134    }
135}
136
137fn check_manager_authority_or_liquidation(
138    user_account: &AccountInfo,
139    fund_info_account: &AccountInfo,
140    fund: &Fund,
141) -> ProgramResult {
142    if FundInfo::new(fund_info_account).get_liquidation_start_time()? > 0 {
143        if !user_account.is_signer {
144            return Err(ProgramError::MissingRequiredSignature);
145        } else {
146            return Ok(());
147        }
148    }
149    check_manager_authority(user_account, fund)
150}
151
152fn check_manager_authority_or_admin_or_liquidation(
153    user_account: &AccountInfo,
154    fund_info_account: &AccountInfo,
155    multisig_account: &AccountInfo,
156    fund: &Fund,
157) -> ProgramResult {
158    if FundInfo::new(fund_info_account).get_liquidation_start_time()? > 0 {
159        if !user_account.is_signer {
160            return Err(ProgramError::MissingRequiredSignature);
161        } else {
162            return Ok(());
163        }
164    }
165    check_manager_authority_or_admin(user_account, multisig_account, fund)
166}
167
168entrypoint!(process_instruction);
169/// Program's entrypoint.
170///
171/// # Arguments
172/// * `program_id` - Public key of the fund.
173/// * `accounts` - Accounts, see handlers in particular strategy for the list.
174/// * `instructions_data` - Packed FundInstruction.
175pub fn process_instruction(
176    program_id: &Pubkey,
177    accounts: &[AccountInfo],
178    instruction_data: &[u8],
179) -> ProgramResult {
180    msg!("Fund entrypoint");
181    if cfg!(feature = "debug") {
182        sol_log_params_short(accounts, instruction_data);
183    }
184
185    let account_info_iter = &mut accounts.iter();
186    let user_account = next_account_info(account_info_iter)?;
187    let fund_metadata = next_account_info(account_info_iter)?;
188    let fund_info_account = next_account_info(account_info_iter)?;
189
190    // unpack Fund's metadata and validate Fund accounts
191    let fund = account::unpack::<Fund>(fund_metadata, "Fund")?;
192    let derived_fund_metadata =
193        refdb::find_target_pda_with_bump(refdb::StorageType::Fund, &fund.name, fund.metadata_bump)?;
194    if &fund.info_account != fund_info_account.key
195        || &derived_fund_metadata != fund_metadata.key
196        || fund_metadata.owner != &main_router::id()
197    {
198        msg!("Error: Invalid Fund accounts");
199        return Err(ProgramError::Custom(511));
200    }
201    if &fund.fund_program_id != program_id {
202        msg!("Error: Invalid Fund program id");
203        return Err(ProgramError::IncorrectProgramId);
204    }
205
206    // Read and unpack instruction data
207    let instruction = FundInstruction::unpack(instruction_data)?;
208
209    match instruction {
210        FundInstruction::UserInit => {
211            log_start("UserInit", &fund.name);
212            user_init(&fund, accounts)?;
213        }
214        FundInstruction::RequestDeposit { amount } => {
215            log_start("RequestDeposit", &fund.name);
216            request_deposit(&fund, accounts, amount)?;
217        }
218        FundInstruction::CancelDeposit => {
219            log_start("CancelDeposit", &fund.name);
220            cancel_deposit(&fund, accounts)?;
221        }
222        FundInstruction::RequestWithdrawal { amount } => {
223            log_start("RequestWithdrawal", &fund.name);
224            request_withdrawal(&fund, accounts, amount)?;
225        }
226        FundInstruction::CancelWithdrawal => {
227            log_start("CancelWithdrawal", &fund.name);
228            cancel_withdrawal(&fund, accounts)?;
229        }
230        FundInstruction::Init { step } => {
231            log_start("Init", &fund.name);
232            if check_admin_authority(accounts, instruction_data, &fund)? {
233                init(&fund, accounts, step)?;
234            }
235        }
236        FundInstruction::SetDepositSchedule { schedule } => {
237            log_start("SetDepositSchedule", &fund.name);
238            check_manager_authority_or_admin(
239                user_account,
240                next_account_info(account_info_iter)?,
241                &fund,
242            )?;
243            set_deposit_schedule(
244                &fund,
245                &mut FundInfo::new(fund_info_account),
246                accounts,
247                &schedule,
248            )?;
249        }
250        FundInstruction::DisableDeposits => {
251            log_start("DisableDeposits", &fund.name);
252            check_manager_authority_or_admin(
253                user_account,
254                next_account_info(account_info_iter)?,
255                &fund,
256            )?;
257            disable_deposits(&fund, &mut FundInfo::new(fund_info_account), accounts)?;
258        }
259        FundInstruction::ApproveDeposit { amount } => {
260            log_start("ApproveDeposit", &fund.name);
261            check_manager_authority_or_admin(
262                user_account,
263                next_account_info(account_info_iter)?,
264                &fund,
265            )?;
266            approve_deposit(&fund, accounts, amount)?;
267        }
268        FundInstruction::DenyDeposit { deny_reason } => {
269            log_start("DenyDeposit", &fund.name);
270            check_manager_authority_or_admin(
271                user_account,
272                next_account_info(account_info_iter)?,
273                &fund,
274            )?;
275            deny_deposit(&fund, accounts, &deny_reason)?;
276        }
277        FundInstruction::SetWithdrawalSchedule { schedule } => {
278            log_start("SetWithdrawalSchedule", &fund.name);
279            check_manager_authority_or_admin(
280                user_account,
281                next_account_info(account_info_iter)?,
282                &fund,
283            )?;
284            set_withdrawal_schedule(
285                &fund,
286                &mut FundInfo::new(fund_info_account),
287                accounts,
288                &schedule,
289            )?;
290        }
291        FundInstruction::DisableWithdrawals => {
292            log_start("DisableWithdrawals", &fund.name);
293            check_manager_authority_or_admin(
294                user_account,
295                next_account_info(account_info_iter)?,
296                &fund,
297            )?;
298            disable_withdrawals(&fund, &mut FundInfo::new(fund_info_account), accounts)?;
299        }
300        FundInstruction::ApproveWithdrawal { amount } => {
301            log_start("ApproveWithdrawal", &fund.name);
302            check_manager_authority_or_admin(
303                user_account,
304                next_account_info(account_info_iter)?,
305                &fund,
306            )?;
307            approve_withdrawal(&fund, accounts, amount)?;
308        }
309        FundInstruction::DenyWithdrawal { deny_reason } => {
310            log_start("DenyWithdrawal", &fund.name);
311            check_manager_authority_or_admin(
312                user_account,
313                next_account_info(account_info_iter)?,
314                &fund,
315            )?;
316            deny_withdrawal(&fund, accounts, &deny_reason)?;
317        }
318        FundInstruction::LockAssets { amount } => {
319            log_start("LockAssets", &fund.name);
320            check_manager_authority_or_admin(
321                user_account,
322                next_account_info(account_info_iter)?,
323                &fund,
324            )?;
325            lock_assets(&fund, accounts, amount)?;
326        }
327        FundInstruction::UnlockAssets { amount } => {
328            log_start("UnlockAssets", &fund.name);
329            check_manager_authority_or_admin_or_liquidation(
330                user_account,
331                fund_info_account,
332                next_account_info(account_info_iter)?,
333                &fund,
334            )?;
335            unlock_assets(&fund, accounts, amount)?;
336        }
337        FundInstruction::SetAssetsTrackingConfig { config } => {
338            log_start("SetAssetsTrackingConfig", &fund.name);
339            if check_admin_authority(accounts, instruction_data, &fund)? {
340                set_assets_tracking_config(
341                    &fund,
342                    &mut FundInfo::new(fund_info_account),
343                    accounts,
344                    &config,
345                )?;
346            }
347        }
348        FundInstruction::UpdateAssetsWithVault => {
349            log_start("UpdateAssetsWithVault", &fund.name);
350            update_assets_with_vault(&fund, accounts)?;
351        }
352        FundInstruction::UpdateAssetsWithCustody => {
353            log_start("UpdateAssetsWithCustody", &fund.name);
354            update_assets_with_custody(&fund, accounts)?;
355        }
356        FundInstruction::AddVault {
357            target_hash,
358            vault_id,
359            vault_type,
360        } => {
361            log_start("AddVault", &fund.name);
362            if check_admin_authority(accounts, instruction_data, &fund)? {
363                add_vault(&fund, accounts, target_hash, vault_id, vault_type)?;
364            }
365        }
366        FundInstruction::RemoveVault {
367            target_hash,
368            vault_type,
369        } => {
370            log_start("RemoveVault", &fund.name);
371            if check_admin_authority(accounts, instruction_data, &fund)? {
372                remove_vault(&fund, accounts, target_hash, vault_type)?;
373            }
374        }
375        FundInstruction::AddCustody {
376            target_hash,
377            custody_id,
378            custody_type,
379        } => {
380            log_start("AddCustody", &fund.name);
381            if check_admin_authority(accounts, instruction_data, &fund)? {
382                add_custody(&fund, accounts, target_hash, custody_id, custody_type)?;
383            }
384        }
385        FundInstruction::RemoveCustody {
386            target_hash,
387            custody_type,
388        } => {
389            log_start("RemoveCustody", &fund.name);
390            if check_admin_authority(accounts, instruction_data, &fund)? {
391                remove_custody(&fund, accounts, target_hash, custody_type)?;
392            }
393        }
394        FundInstruction::StartLiquidation => {
395            log_start("StartLiquidation", &fund.name);
396            start_liquidation(&fund, accounts)?;
397        }
398        FundInstruction::StopLiquidation => {
399            log_start("StopLiquidation", &fund.name);
400            if check_admin_authority(accounts, instruction_data, &fund)? {
401                stop_liquidation(&fund, accounts)?;
402            }
403        }
404        FundInstruction::WithdrawFees { amount } => {
405            log_start("WithdrawFees", &fund.name);
406            if check_admin_authority(accounts, instruction_data, &fund)? {
407                withdraw_fees(&fund, accounts, amount)?;
408            }
409        }
410        FundInstruction::SetAdminSigners { min_signatures } => {
411            log_start("SetAdminSigners", &fund.name);
412            if check_admin_authority(accounts, instruction_data, &fund)? {
413                set_admin_signers(&fund, accounts, min_signatures)?;
414            }
415        }
416        FundInstruction::RemoveMultisig => {
417            log_start("RemoveMultisig", &fund.name);
418            if check_admin_authority(accounts, instruction_data, &fund)? {
419                remove_multisig(&fund, accounts)?;
420            }
421        }
422        FundInstruction::AmmInstructionRaydium { instruction } => match instruction {
423            AmmInstruction::UserInit => {
424                log_start("UserInitRaydium", &fund.name);
425                check_manager_authority(user_account, &fund)?;
426                raydium::user_init::user_init(&fund, accounts)?;
427            }
428            AmmInstruction::AddLiquidity {
429                max_token_a_amount,
430                max_token_b_amount,
431            } => {
432                log_start("AddLiquidityRaydium", &fund.name);
433                check_manager_authority(user_account, &fund)?;
434                raydium::add_liquidity::add_liquidity(
435                    &fund,
436                    accounts,
437                    max_token_a_amount,
438                    max_token_b_amount,
439                )?;
440            }
441            AmmInstruction::RemoveLiquidity { amount } => {
442                log_start("RemoveLiquidityRaydium", &fund.name);
443                check_manager_authority_or_liquidation(user_account, fund_info_account, &fund)?;
444                raydium::remove_liquidity::remove_liquidity(&fund, accounts, amount)?;
445            }
446            AmmInstruction::Swap {
447                token_a_amount_in,
448                token_b_amount_in,
449                min_token_amount_out,
450            } => {
451                log_start("SwapRaydium", &fund.name);
452                check_manager_authority_or_liquidation(user_account, fund_info_account, &fund)?;
453                raydium::swap::swap(
454                    &fund,
455                    accounts,
456                    token_a_amount_in,
457                    token_b_amount_in,
458                    min_token_amount_out,
459                )?;
460            }
461            AmmInstruction::Stake { amount } => {
462                log_start("StakeRaydium", &fund.name);
463                check_manager_authority(user_account, &fund)?;
464                raydium::stake::stake(&fund, accounts, amount, false)?;
465            }
466            AmmInstruction::Unstake { amount } => {
467                log_start("UnstakeRaydium", &fund.name);
468                check_manager_authority_or_liquidation(user_account, fund_info_account, &fund)?;
469                raydium::unstake::unstake(&fund, accounts, amount)?;
470            }
471            AmmInstruction::Harvest => {
472                log_start("HarvestRaydium", &fund.name);
473                check_manager_authority_or_liquidation(user_account, fund_info_account, &fund)?;
474                raydium::stake::stake(&fund, accounts, 0, true)?;
475            }
476            _ => {
477                msg!("Error: Unimplemented");
478                return Err(ProgramError::Custom(512));
479            }
480        },
481        FundInstruction::VaultInstructionRaydium { instruction } => match instruction {
482            VaultInstruction::AddLiquidity {
483                max_token_a_amount,
484                max_token_b_amount,
485            } => {
486                log_start("VaultAddLiquidityRaydium", &fund.name);
487                check_manager_authority(user_account, &fund)?;
488                raydium::vault_add_liquidity::add_liquidity(
489                    &fund,
490                    accounts,
491                    max_token_a_amount,
492                    max_token_b_amount,
493                )?;
494            }
495            VaultInstruction::LockLiquidity { amount } => {
496                log_start("VaultLockLiquidityRaydium", &fund.name);
497                check_manager_authority(user_account, &fund)?;
498                raydium::vault_lock_liquidity::lock_liquidity(&fund, accounts, amount)?;
499            }
500            VaultInstruction::UnlockLiquidity { amount } => {
501                log_start("VaultUnlockLiquidityRaydium", &fund.name);
502                check_manager_authority_or_liquidation(user_account, fund_info_account, &fund)?;
503                raydium::vault_unlock_liquidity::unlock_liquidity(&fund, accounts, amount)?;
504            }
505            VaultInstruction::RemoveLiquidity { amount } => {
506                log_start("VaultRemoveLiquidityRaydium", &fund.name);
507                check_manager_authority_or_liquidation(user_account, fund_info_account, &fund)?;
508                raydium::vault_remove_liquidity::remove_liquidity(&fund, accounts, amount)?;
509            }
510            VaultInstruction::UserInit {} => {
511                log_start("VaultUserInitRaydium", &fund.name);
512                check_manager_authority(user_account, &fund)?;
513                raydium::vault_user_init::user_init(&fund, accounts)?;
514            }
515            _ => {
516                msg!("Error: Unimplemented");
517                return Err(ProgramError::Custom(513));
518            }
519        },
520        FundInstruction::AmmInstructionOrca { instruction } => match instruction {
521            AmmInstruction::UserInit => {
522                log_start("UserInitOrca", &fund.name);
523                check_manager_authority(user_account, &fund)?;
524                orca::user_init::user_init(&fund, accounts)?;
525            }
526            AmmInstruction::AddLiquidity {
527                max_token_a_amount,
528                max_token_b_amount,
529            } => {
530                log_start("AddLiquidityOrca", &fund.name);
531                check_manager_authority(user_account, &fund)?;
532                orca::add_liquidity::add_liquidity(
533                    &fund,
534                    accounts,
535                    max_token_a_amount,
536                    max_token_b_amount,
537                )?;
538            }
539            AmmInstruction::RemoveLiquidity { amount } => {
540                log_start("RemoveLiquidityOrca", &fund.name);
541                check_manager_authority_or_liquidation(user_account, fund_info_account, &fund)?;
542                orca::remove_liquidity::remove_liquidity(&fund, accounts, amount)?;
543            }
544            AmmInstruction::Swap {
545                token_a_amount_in,
546                token_b_amount_in,
547                min_token_amount_out,
548            } => {
549                log_start("SwapOrca", &fund.name);
550                check_manager_authority_or_liquidation(user_account, fund_info_account, &fund)?;
551                orca::swap::swap(
552                    &fund,
553                    accounts,
554                    token_a_amount_in,
555                    token_b_amount_in,
556                    min_token_amount_out,
557                )?;
558            }
559            AmmInstruction::Stake { amount } => {
560                log_start("StakeOrca", &fund.name);
561                check_manager_authority(user_account, &fund)?;
562                orca::stake::stake(&fund, accounts, amount)?;
563            }
564            AmmInstruction::Unstake { amount } => {
565                log_start("UnstakeOrca", &fund.name);
566                check_manager_authority_or_liquidation(user_account, fund_info_account, &fund)?;
567                orca::unstake::unstake(&fund, accounts, amount)?;
568            }
569            AmmInstruction::Harvest => {
570                log_start("HarvestOrca", &fund.name);
571                check_manager_authority_or_liquidation(user_account, fund_info_account, &fund)?;
572                orca::harvest::harvest(&fund, accounts)?;
573            }
574            _ => {
575                msg!("Error: Unimplemented");
576                return Err(ProgramError::Custom(512));
577            }
578        },
579        FundInstruction::VaultInstructionOrca { instruction } => match instruction {
580            VaultInstruction::AddLiquidity {
581                max_token_a_amount,
582                max_token_b_amount,
583            } => {
584                log_start("VaultAddLiquidityOrca", &fund.name);
585                check_manager_authority(user_account, &fund)?;
586                orca::vault_add_liquidity::add_liquidity(
587                    &fund,
588                    accounts,
589                    max_token_a_amount,
590                    max_token_b_amount,
591                )?;
592            }
593            VaultInstruction::LockLiquidity { amount } => {
594                log_start("VaultLockLiquidityOrca", &fund.name);
595                check_manager_authority(user_account, &fund)?;
596                orca::vault_lock_liquidity::lock_liquidity(&fund, accounts, amount)?;
597            }
598            VaultInstruction::UnlockLiquidity { amount } => {
599                log_start("VaultUnlockLiquidityOrca", &fund.name);
600                check_manager_authority_or_liquidation(user_account, fund_info_account, &fund)?;
601                orca::vault_unlock_liquidity::unlock_liquidity(&fund, accounts, amount)?;
602            }
603            VaultInstruction::RemoveLiquidity { amount } => {
604                log_start("VaultRemoveLiquidityOrca", &fund.name);
605                check_manager_authority_or_liquidation(user_account, fund_info_account, &fund)?;
606                orca::vault_remove_liquidity::remove_liquidity(&fund, accounts, amount)?;
607            }
608            VaultInstruction::UserInit {} => {
609                log_start("VaultUserInitOrca", &fund.name);
610                check_manager_authority(user_account, &fund)?;
611                orca::vault_user_init::user_init(&fund, accounts)?;
612            }
613            _ => {
614                msg!("Error: Unimplemented");
615                return Err(ProgramError::Custom(513));
616            }
617        },
618    }
619
620    log_end(&fund.name);
621    Ok(())
622}