Skip to content

Commit

Permalink
Merge pull request #21 from insight-extension/apps/deposit-program
Browse files Browse the repository at this point in the history
apps/deposit program
  • Loading branch information
tsukigatari authored Nov 6, 2024
2 parents 8cf3eb0 + c11b85e commit aba8cf5
Show file tree
Hide file tree
Showing 12 changed files with 3,584 additions and 3,996 deletions.
4 changes: 2 additions & 2 deletions apps/deposit-program/programs/deposit-program/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ pub enum ErrorCode {
InsufficientBalance,
#[msg("Unauthorized Withdrawal")]
UnauthorizedWithdrawal,
#[msg("Invalid Master Wallet")]
InvalidMasterWallet,
#[msg("Already subscribed")]
AlreadySubscribed,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use anchor_lang::prelude::*;

use anchor_spl::{
associated_token::AssociatedToken,
token_interface::{Mint, TokenAccount, TokenInterface},
};

use crate::{send_tokens, UserInfo, USER_INFO_SEED};

#[derive(Accounts)]
pub struct DepositToVault<'info> {
#[account(mut)]
pub user: Signer<'info>,
#[account(mint::token_program = token_program)]
pub token: InterfaceAccount<'info, Mint>,
#[account(
mut,
associated_token::mint = token,
associated_token::authority = user,
associated_token::token_program = token_program,
)]
pub user_token_account: InterfaceAccount<'info, TokenAccount>,
#[account(
init_if_needed,
payer = user,
space = 8 + UserInfo::INIT_SPACE,
seeds = [USER_INFO_SEED, user.key().as_ref()],
bump
)]
user_info: Account<'info, UserInfo>,
#[account(
init_if_needed,
payer = user,
associated_token::mint = token,
associated_token::authority = user_info,
associated_token::token_program = token_program,
)]
pub vault: InterfaceAccount<'info, TokenAccount>,
pub associated_token_program: Program<'info, AssociatedToken>,
pub token_program: Interface<'info, TokenInterface>,
pub system_program: Program<'info, System>,
}

pub fn deposit_to_vault_handler(ctx: Context<DepositToVault>, amount: u64) -> Result<()> {
send_tokens(
ctx.accounts.user_token_account.to_account_info(),
ctx.accounts.token.to_account_info(),
ctx.accounts.vault.to_account_info(),
ctx.accounts.user.to_account_info(),
ctx.accounts.token_program.to_account_info(),
ctx.accounts.token.decimals,
amount,
)?;
save_user_info(ctx, amount)?;
msg!("Deposited {} tokens to vault.", amount);
Ok(())
}

fn save_user_info(ctx: Context<DepositToVault>, amount: u64) -> Result<()> {
ctx.accounts.user_info.available_balance += amount;
ctx.accounts.user_info.bump = ctx.bumps.user_info;
Ok(())
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,9 @@ pub use subscribe::*;

pub mod refund_balance;
pub use refund_balance::*;

pub mod deposit_to_vault;
pub use deposit_to_vault::*;

pub mod subscribe_with_vault;
pub use subscribe_with_vault::*;
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ pub struct RefundBalance<'info> {
pub user: Signer<'info>, // User must sign the transaction
#[account(mint::token_program = token_program)]
pub token: InterfaceAccount<'info, Mint>,
#[account(mut,
#[account(
mut,
seeds = [USER_INFO_SEED, user.key().as_ref()],
bump = user_info.bump
)]
Expand All @@ -36,7 +37,7 @@ pub struct RefundBalance<'info> {
pub system_program: Program<'info, System>,
}

pub fn handler(ctx: Context<RefundBalance>) -> Result<()> {
pub fn refund_balance_handler(ctx: Context<RefundBalance>) -> Result<()> {
let amount = ctx.accounts.user_info.available_balance;
if amount == 0 {
return Err(ErrorCode::InsufficientBalance.into());
Expand All @@ -49,7 +50,7 @@ pub fn handler(ctx: Context<RefundBalance>) -> Result<()> {
Ok(())
}

pub fn send_to_user(ctx: &Context<RefundBalance>, amount: u64) -> Result<()> {
fn send_to_user(ctx: &Context<RefundBalance>, amount: u64) -> Result<()> {
let user_key = ctx.accounts.user.key();
let seeds = &[
USER_INFO_SEED,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ use anchor_lang::prelude::*;

use anchor_spl::{
associated_token::AssociatedToken,
token_interface::{transfer_checked, Mint, TokenAccount, TokenInterface, TransferChecked},
token_interface::{Mint, TokenAccount, TokenInterface},
};

use crate::{error::ErrorCode, UserInfo, LOCAL_MASTER_WALLET, SUBSCRIPTION_LEVELS, USER_INFO_SEED};
use crate::{
error::ErrorCode, get_subscription_level, send_tokens, UserInfo, LOCAL_MASTER_WALLET,
USER_INFO_SEED,
};

#[derive(Accounts)]
pub struct Subscribe<'info> {
Expand Down Expand Up @@ -38,11 +41,10 @@ pub struct Subscribe<'info> {
pub vault: InterfaceAccount<'info, TokenAccount>,
/// See Anchor example:
/// https://github.com/solana-developers/anchor-examples/blob/main/account-constraints/address/programs/example/src/lib.rs
/// CHECK: Restricts `master_wallet` to the `MASTER_WALLET` address.
#[account(
address = LOCAL_MASTER_WALLET
)]
pub master_wallet: UncheckedAccount<'info>,
pub master_wallet: SystemAccount<'info>,
#[account(
mut,
associated_token::mint = token,
Expand All @@ -55,23 +57,18 @@ pub struct Subscribe<'info> {
pub system_program: Program<'info, System>,
}

pub fn handler(ctx: Context<Subscribe>, amount: u64) -> Result<()> {
let (subscription_cost, duration) = match SUBSCRIPTION_LEVELS
.iter()
.rev()
.find(|(cost, _)| amount >= *cost)
{
Some(level) => *level,
None => return Err(ErrorCode::InsufficientBalance.into()),
};
pub fn subscribe_handler(ctx: Context<Subscribe>, amount: u64) -> Result<()> {
let (subscription_cost, duration) = get_subscription_level(amount)?;
// Calculate the amount to send to the vault
let vault_amount = amount.saturating_sub(subscription_cost); // Ensure no negative value
let vault_amount = amount - subscription_cost;
let current_timestamp = Clock::get()?.unix_timestamp;
// Check if the user already has an active subscription
if ctx.accounts.user_info.expiration > current_timestamp {
msg!("Active subscription found. Updating user's available balance.");
send_to_vault(&ctx, amount)?; // Transfer entire amount to vault
update_user_info(ctx, amount)?;
msg!("User already has an active subscription.");
return Err(ErrorCode::AlreadySubscribed.into());
//msg!("Active subscription found. Updating user's available balance.");
//send_to_vault(&ctx, amount)?; // Transfer entire amount to vault
//update_user_info(ctx, amount)?;
//return Err(Error::from(SubscribeErrorCode::AlreadySubscribed));
} else {
msg!("No active subscription found. Proceeding with new subscription.");
Expand All @@ -85,49 +82,40 @@ pub fn handler(ctx: Context<Subscribe>, amount: u64) -> Result<()> {
Ok(())
}

pub fn update_user_info(ctx: Context<Subscribe>, additional_balance: u64) -> Result<()> {
ctx.accounts.user_info.available_balance += additional_balance;
msg!("Subscription is active. Funds have been added to the vault, and your available balance has been updated.");
Ok(())
}
//fn update_user_info(ctx: Context<Subscribe>, additional_balance: u64) -> Result<()> {
// ctx.accounts.user_info.available_balance += additional_balance;
// msg!("Subscription is active. Funds have been added to the vault, and your available balance has been updated.");
// Ok(())
//}

pub fn send_to_master_wallet(ctx: &Context<Subscribe>, amount: u64) -> Result<()> {
let transfer_accounts = TransferChecked {
from: ctx.accounts.user_token_account.to_account_info(),
mint: ctx.accounts.token.to_account_info(),
to: ctx.accounts.master_wallet_token_account.to_account_info(),
authority: ctx.accounts.user.to_account_info(),
};
let cpi_context = CpiContext::new(
fn send_to_master_wallet(ctx: &Context<Subscribe>, amount: u64) -> Result<()> {
send_tokens(
ctx.accounts.user_token_account.to_account_info(),
ctx.accounts.token.to_account_info(),
ctx.accounts.master_wallet_token_account.to_account_info(),
ctx.accounts.user.to_account_info(),
ctx.accounts.token_program.to_account_info(),
transfer_accounts,
);
transfer_checked(cpi_context, amount, ctx.accounts.token.decimals)?;
ctx.accounts.token.decimals,
amount,
)?;
msg!("Transferred {} tokens to master wallet.", amount);
Ok(())
}

pub fn send_to_vault(ctx: &Context<Subscribe>, amount: u64) -> Result<()> {
let transfer_accounts = TransferChecked {
from: ctx.accounts.user_token_account.to_account_info(),
mint: ctx.accounts.token.to_account_info(),
to: ctx.accounts.vault.to_account_info(),
authority: ctx.accounts.user.to_account_info(),
};
let cpi_context = CpiContext::new(
fn send_to_vault(ctx: &Context<Subscribe>, amount: u64) -> Result<()> {
send_tokens(
ctx.accounts.user_token_account.to_account_info(),
ctx.accounts.token.to_account_info(),
ctx.accounts.vault.to_account_info(),
ctx.accounts.user.to_account_info(),
ctx.accounts.token_program.to_account_info(),
transfer_accounts,
);
transfer_checked(cpi_context, amount, ctx.accounts.token.decimals)?;
msg!("Transferred {} tokens to vault.", amount);
ctx.accounts.token.decimals,
amount,
)?;
Ok(())
}

pub fn save_user_info(
ctx: Context<Subscribe>,
available_balance: u64,
duration: u64,
) -> Result<()> {
fn save_user_info(ctx: Context<Subscribe>, available_balance: u64, duration: u64) -> Result<()> {
let current_timestamp = Clock::get()?.unix_timestamp;
let expiration = current_timestamp + duration as i64;
let bump = ctx.bumps.user_info;
Expand All @@ -136,6 +124,5 @@ pub fn save_user_info(
expiration,
bump,
});
msg!("New subscription saved with updated expiration.");
Ok(())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
use anchor_lang::prelude::*;

use anchor_spl::{
associated_token::AssociatedToken,
token_interface::{transfer_checked, Mint, TokenAccount, TokenInterface, TransferChecked},
};

use crate::{
error::ErrorCode, get_subscription_level, UserInfo, LOCAL_MASTER_WALLET, USER_INFO_SEED,
};

#[derive(Accounts)]
pub struct SubscribeWithVault<'info> {
#[account(mut)]
pub user: Signer<'info>,
#[account(mint::token_program = token_program)]
pub token: InterfaceAccount<'info, Mint>,
#[account(
mut,
seeds = [USER_INFO_SEED, user.key().as_ref()],
bump = user_info.bump
)]
pub user_info: Account<'info, UserInfo>,
#[account(
address = LOCAL_MASTER_WALLET
)]
pub master_wallet: SystemAccount<'info>,
#[account(
mut,
associated_token::mint = token,
associated_token::authority = master_wallet,
associated_token::token_program = token_program
)]
pub master_wallet_token_account: InterfaceAccount<'info, TokenAccount>,
#[account(
mut,
associated_token::mint = token,
associated_token::authority = user_info,
associated_token::token_program = token_program,
)]
pub vault: InterfaceAccount<'info, TokenAccount>,
pub associated_token_program: Program<'info, AssociatedToken>,
pub token_program: Interface<'info, TokenInterface>,
pub system_program: Program<'info, System>,
}

pub fn subscribe_with_vault_handler(ctx: Context<SubscribeWithVault>, amount: u64) -> Result<()> {
let current_timestamp = Clock::get()?.unix_timestamp;
if ctx.accounts.user_info.expiration > current_timestamp {
return Err(ErrorCode::AlreadySubscribed.into());
} else if ctx.accounts.user_info.available_balance < amount {
return Err(ErrorCode::InsufficientBalance.into());
} else {
let (subscription_cost, duration) = get_subscription_level(amount)?;
send_to_master_wallet(&ctx, subscription_cost)?;
msg!(
"Subscription cost of {} tokens has been sent to the master wallet.",
subscription_cost
);
update_user_info(ctx, amount, duration)?;
}
msg!("Subscription successful.");
Ok(())
}

fn send_to_master_wallet(ctx: &Context<SubscribeWithVault>, amount: u64) -> Result<()> {
let seeds = &[
USER_INFO_SEED,
ctx.accounts.user.key.as_ref(),
&[ctx.accounts.user_info.bump],
];
let signer_seeds = &[&seeds[..]];
let transfer_accounts = TransferChecked {
from: ctx.accounts.vault.to_account_info(),
mint: ctx.accounts.token.to_account_info(),
to: ctx.accounts.master_wallet_token_account.to_account_info(),
authority: ctx.accounts.user_info.to_account_info(),
};
let cpi_context = CpiContext::new_with_signer(
ctx.accounts.token_program.to_account_info(),
transfer_accounts,
signer_seeds,
);
transfer_checked(cpi_context, amount, ctx.accounts.token.decimals)?;
Ok(())
}

fn update_user_info(ctx: Context<SubscribeWithVault>, amount: u64, duration: u64) -> Result<()> {
let current_timestamp = Clock::get()?.unix_timestamp;
let expiration = current_timestamp + duration as i64;
ctx.accounts.user_info.available_balance -= amount;
ctx.accounts.user_info.expiration = expiration;
Ok(())
}
14 changes: 12 additions & 2 deletions apps/deposit-program/programs/deposit-program/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
pub mod constants;
pub mod error;
pub mod instructions;
pub mod reusable;
pub mod state;

use anchor_lang::prelude::*;

pub use constants::*;
pub use instructions::*;
pub use reusable::*;
pub use state::*;

declare_id!("6vsijynzE22W8A4kkvv2Kq7a36ZEFPvJP9kBgNtN43mK");
Expand All @@ -16,10 +18,18 @@ pub mod deposit_program {
use super::*;

pub fn subscribe(ctx: Context<Subscribe>, amount: u64) -> Result<()> {
instructions::subscribe::handler(ctx, amount)
instructions::subscribe::subscribe_handler(ctx, amount)
}

pub fn refund_balance(ctx: Context<RefundBalance>) -> Result<()> {
instructions::refund_balance::handler(ctx)
instructions::refund_balance::refund_balance_handler(ctx)
}

pub fn deposit_to_vault(ctx: Context<DepositToVault>, amount: u64) -> Result<()> {
instructions::deposit_to_vault::deposit_to_vault_handler(ctx, amount)
}

pub fn subscribe_with_vault(ctx: Context<SubscribeWithVault>, amount: u64) -> Result<()> {
instructions::subscribe_with_vault::subscribe_with_vault_handler(ctx, amount)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use crate::{error::ErrorCode, SUBSCRIPTION_LEVELS};

pub fn get_subscription_level(amount: u64) -> Result<(u64, u64), ErrorCode> {
match SUBSCRIPTION_LEVELS
.iter()
.rev()
.find(|(cost, _)| amount >= *cost)
{
Some(level) => Ok(*level),
None => Err(ErrorCode::InsufficientBalance),
}
}
Loading

0 comments on commit aba8cf5

Please sign in to comment.