Skip to content

Commit

Permalink
Refactor SDK
Browse files Browse the repository at this point in the history
  • Loading branch information
yurushao committed Jan 26, 2025
1 parent 8b8d58c commit a41158a
Show file tree
Hide file tree
Showing 13 changed files with 1,899 additions and 477 deletions.
4 changes: 2 additions & 2 deletions anchor/libs/macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ pub fn share_class_signer_seeds(_attr: TokenStream, item: TokenStream) -> TokenS
// We assume the fund account and the treasury bump seed are available in the context
let state_key = ctx.accounts.state.key();
let seeds = &[
"share".as_bytes(),
"mint".as_bytes(),
&[share_class_id],
state_key.as_ref(),
&[ctx.bumps.share_class_mint],
Expand Down Expand Up @@ -71,7 +71,7 @@ pub fn vault_signer_seeds(_attr: TokenStream, item: TokenStream) -> TokenStream
// We assume the fund account and the vault bump seed are available in the context
let state_key = ctx.accounts.state.key();
let seeds = [
"treasury".as_ref(),
"vault".as_ref(),
state_key.as_ref(),
&[ctx.bumps.vault],
];
Expand Down
75 changes: 41 additions & 34 deletions anchor/programs/glam/src/instructions/share_class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,8 @@ pub struct AddShareClass<'info> {
#[account(mut, constraint = state.owner == signer.key() @ AccessError::NotAuthorized)]
pub state: Box<Account<'info, StateAccount>>,

/// CHECK: Metadata
#[account(mut, seeds = [SEED_METADATA.as_bytes(), state.key().as_ref()], bump)]
pub metadata: AccountInfo<'info>,
pub openfunds_metadata: Option<Box<Account<'info, OpenfundsMetadataAccount>>>,

#[account(mut)]
pub signer: Signer<'info>,
Expand Down Expand Up @@ -87,13 +86,13 @@ pub fn add_share_class_handler<'c: 'info, 'info>(
EngineField {
name: EngineFieldName::ShareClassAllowlist,
value: EngineFieldValue::VecPubkey {
val: share_class_metadata.allowlist.clone(),
val: share_class_metadata.clone().allowlist.unwrap_or_default(),
},
},
EngineField {
name: EngineFieldName::ShareClassBlocklist,
value: EngineFieldValue::VecPubkey {
val: share_class_metadata.blocklist.clone(),
val: share_class_metadata.clone().blocklist.unwrap_or_default(),
},
},
];
Expand All @@ -110,24 +109,30 @@ pub fn add_share_class_handler<'c: 'info, 'info>(
// Output:
// - has_lock_up_for_redemption (openfunds)
// - lock_up_period_in_days (openfunds)
let policy_has_lock_up = share_class_metadata.lock_up_period_in_seconds > 0;
if policy_has_lock_up {
share_class_params.push(EngineField {
name: EngineFieldName::LockUp,
value: EngineFieldValue::Timestamp {
// lock_up_period_in_seconds is i32 so it's easier to use in js,
// we can express it as a number instead of requiring BN.
// the max lock up is 24k+ days, so it should be good.
val: share_class_metadata.lock_up_period_in_seconds.into(),
},
});
raw_openfunds.lock_up_period_in_days =
Some((1 + share_class_metadata.lock_up_period_in_seconds / 24 * 60 * 60).to_string());
} else {
raw_openfunds.lock_up_period_in_days = None;
raw_openfunds.lock_up_comment = None;

let mut transfer_hook_active = false;
if let Some(lock_up_period_in_seconds) = share_class_metadata.lock_up_period_in_seconds {
let policy_has_lock_up = lock_up_period_in_seconds > 0;
transfer_hook_active = policy_has_lock_up;

if policy_has_lock_up {
share_class_params.push(EngineField {
name: EngineFieldName::LockUp,
value: EngineFieldValue::Timestamp {
// lock_up_period_in_seconds is i32 so it's easier to use in js,
// we can express it as a number instead of requiring BN.
// the max lock up is 24k+ days, so it should be good.
val: lock_up_period_in_seconds.into(),
},
});
raw_openfunds.lock_up_period_in_days =
Some((1 + lock_up_period_in_seconds / 24 * 60 * 60).to_string());
} else {
raw_openfunds.lock_up_period_in_days = None;
raw_openfunds.lock_up_comment = None;
}
raw_openfunds.has_lock_up_for_redemption = Some(policy_has_lock_up);
}
raw_openfunds.has_lock_up_for_redemption = Some(policy_has_lock_up);

share_class_metadata.raw_openfunds = Some(raw_openfunds);
state.params.push(share_class_params);
Expand All @@ -138,11 +143,12 @@ pub fn add_share_class_handler<'c: 'info, 'info>(
// Add share class data metadata, currently only openfunds metadata is supported
//
if let Some(metadata) = state.metadata.clone() {
if metadata.template == MetadataType::Openfunds {
let mut data_slice = ctx.accounts.metadata.data.borrow_mut(); // Borrow the mutable data
let data: &mut [u8] = &mut *data_slice; // Dereference and convert to `&mut [u8]`
let mut openfunds = OpenfundsMetadataAccount::try_deserialize(&mut &data[..])?; // Pass as `&mut &[u8]`
openfunds.share_classes.push(share_class_fields.clone());
if metadata.template == MetadataTemplate::Openfunds {
if let Some(openfunds_metadata) = &mut ctx.accounts.openfunds_metadata {
openfunds_metadata
.share_classes
.push(share_class_fields.clone());
}
}
}

Expand All @@ -158,17 +164,18 @@ pub fn add_share_class_handler<'c: 'info, 'info>(
other => other,
};

let default_account_state_frozen = share_class_metadata.default_account_state_frozen;
let default_account_state_frozen = share_class_metadata
.default_account_state_frozen
.unwrap_or(false);

let seeds = &[
"share".as_bytes(),
SEED_MINT.as_bytes(),
&[share_class_idx],
state_key.as_ref(),
&[ctx.bumps.share_class_mint],
];
let signer_seeds = &[&seeds[..]];

let transfer_hook_active = policy_has_lock_up;
let mut extension_types = vec![
ExtensionType::MetadataPointer, // always present
ExtensionType::MintCloseAuthority, // always present
Expand Down Expand Up @@ -646,18 +653,18 @@ pub fn update_share_class_handler(
share_class_model: ShareClassModel,
) -> Result<()> {
let state = &mut ctx.accounts.state;
if !share_class_model.allowlist.is_empty() {
if let Some(share_class_allowlist) = share_class_model.allowlist {
let allowlist = state.share_class_allowlist_mut(share_class_id as usize);
if let Some(_allowlist) = allowlist {
_allowlist.clear();
_allowlist.extend(share_class_model.allowlist.clone());
_allowlist.extend(share_class_allowlist.clone());
}
}
if !share_class_model.blocklist.is_empty() {
if let Some(share_class_blocklist) = share_class_model.blocklist {
let blocklist = state.share_class_blocklist_mut(share_class_id as usize);
if let Some(_blocklist) = blocklist {
_blocklist.clear();
_blocklist.extend(share_class_model.blocklist.clone());
_blocklist.extend(share_class_blocklist.clone());
}
}
Ok(())
Expand Down Expand Up @@ -730,7 +737,7 @@ pub fn close_share_class_handler(ctx: Context<CloseShareClass>, share_class_id:
ctx.accounts.state.mints.remove(share_class_id as usize);

if let Some(metadata) = ctx.accounts.state.metadata.clone() {
if metadata.template == MetadataType::Openfunds {
if metadata.template == MetadataTemplate::Openfunds {
let mut data_slice = ctx.accounts.metadata.data.borrow_mut(); // Borrow the mutable data
let data: &mut [u8] = &mut *data_slice; // Dereference and convert to `&mut [u8]`
let mut openfunds = OpenfundsMetadataAccount::try_deserialize(&mut &data[..])?;
Expand Down
59 changes: 21 additions & 38 deletions anchor/programs/glam/src/instructions/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::{
error::{AccessError, StateError},
state::*,
};
use anchor_lang::{prelude::*, solana_program, system_program, Discriminator};
use anchor_lang::{prelude::*, solana_program, system_program};
use anchor_spl::{
token::{close_account as close_token_account, CloseAccount as CloseTokenAccount, Token},
token_2022::{
Expand Down Expand Up @@ -35,9 +35,14 @@ pub struct InitializeState<'info> {
#[account(mut)]
pub signer: Signer<'info>,

/// CHECK: Manually initialized metadata account according to template provided in state model
#[account(mut, seeds = [SEED_METADATA.as_bytes(), state.key().as_ref()], bump)]
pub metadata: AccountInfo<'info>,
#[account(
init,
seeds = [SEED_METADATA.as_bytes(), state.key().as_ref()],
bump,
payer = signer,
space = 8 + OpenfundsMetadataAccount::INIT_SIZE
)]
pub openfunds_metadata: Option<Box<Account<'info, OpenfundsMetadataAccount>>>,

pub system_program: Program<'info, System>,
}
Expand All @@ -62,6 +67,13 @@ pub fn initialize_state_handler<'c: 'info, 'info>(
require!(uri.len() < MAX_SIZE_URI, StateError::InvalidUri);
state.uri = uri;
}
if let Some(created) = model.created {
state.created = CreatedModel {
key: created.key,
created_by: ctx.accounts.signer.key(),
created_at: Clock::get()?.unix_timestamp,
};
}
if let Some(metadata) = model.metadata {
require!(metadata.uri.len() < MAX_SIZE_URI, StateError::InvalidUri);
state.metadata = Some(Metadata {
Expand All @@ -70,45 +82,16 @@ pub fn initialize_state_handler<'c: 'info, 'info>(
uri: metadata.uri,
});

if metadata.template == MetadataType::Openfunds {
let rent = Rent::get()?;
let space = 8 + OpenfundsMetadataAccount::INIT_SIZE;
let lamports = rent.minimum_balance(space);

let create_account_ix = solana_program::system_instruction::create_account(
ctx.accounts.signer.key,
ctx.accounts.metadata.key,
lamports,
space as u64,
&crate::ID,
);

solana_program::program::invoke_signed(
&create_account_ix,
&[
ctx.accounts.signer.to_account_info(),
ctx.accounts.metadata.to_account_info(),
ctx.accounts.system_program.to_account_info(),
],
&[&[
SEED_METADATA.as_bytes(),
state.key().as_ref(),
&[ctx.bumps.metadata],
]],
)?;

let discriminator = OpenfundsMetadataAccount::discriminator();
let mut metadata_data = ctx.accounts.metadata.try_borrow_mut_data()?;
metadata_data[..8].copy_from_slice(&discriminator);

let openfunds_metadata = OpenfundsMetadataAccount::from(state_model);
openfunds_metadata.try_serialize(&mut &mut metadata_data[8..])?;
if metadata.template == MetadataTemplate::Openfunds {
if let Some(openfunds_metadata) = &mut ctx.accounts.openfunds_metadata {
openfunds_metadata.set_inner(OpenfundsMetadataAccount::from(state_model));
openfunds_metadata.fund_id = state.key();
}
}
}

state.vault = ctx.accounts.vault.key();
state.owner = ctx.accounts.signer.key();
state.created = Clock::get()?.unix_timestamp;
state.enabled = model.enabled.unwrap_or(true);
state.assets = model.assets.unwrap_or_default();
state.integrations = model.integrations.unwrap_or_default();
Expand Down
15 changes: 11 additions & 4 deletions anchor/programs/glam/src/state/accounts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,25 +51,32 @@ pub enum AccountType {
}

#[derive(AnchorDeserialize, AnchorSerialize, Clone, Debug, PartialEq, Copy)]
pub enum MetadataType {
pub enum MetadataTemplate {
Openfunds,
// ... more metadata types
// ... more metadata templates
}

#[derive(AnchorDeserialize, AnchorSerialize, Clone, Debug)]
pub struct Metadata {
pub template: MetadataType,
pub template: MetadataTemplate,
pub pubkey: Pubkey, // metadata account pubkey
pub uri: String,
}

#[derive(AnchorDeserialize, AnchorSerialize, Clone, Debug)]
pub struct CreatedModel {
pub key: [u8; 8], // seed for computing state PDA
pub created_by: Pubkey, // original pubkey that created the state account
pub created_at: i64,
}

#[account]
pub struct StateAccount {
pub account_type: AccountType,
pub owner: Pubkey,
pub vault: Pubkey,
pub enabled: bool,
pub created: i64,
pub created: CreatedModel,
pub engine: Pubkey,
pub mints: Vec<Pubkey>,
pub metadata: Option<Metadata>,
Expand Down
25 changes: 7 additions & 18 deletions anchor/programs/glam/src/state/model/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,10 @@ use crate::state::accounts::*;

use super::super::acl::*;

// Fund
//
// Implemented:
// - Openfunds Fund Essential + Core

#[derive(AnchorDeserialize, AnchorSerialize, Clone, Debug)]
pub struct StateModel {
// Core
pub id: Option<Pubkey>,
pub account_type: Option<AccountType>,
pub name: Option<String>,
pub uri: Option<String>,
Expand Down Expand Up @@ -60,13 +56,6 @@ pub struct FundOpenfundsModel {
pub ucits_version: Option<String>,
}

#[derive(AnchorDeserialize, AnchorSerialize, Clone, Debug)]
pub struct CreatedModel {
pub key: [u8; 8], // seed for computing state PDA
pub created_at: i64,
pub owner: Option<Pubkey>,
}

// Share Class
//
// Implemented:
Expand All @@ -83,19 +72,19 @@ pub struct ShareClassModel {
// Glam
pub state_pubkey: Option<Pubkey>,
pub asset: Option<Pubkey>,
pub image_uri: Option<String>,
pub image_uri: Option<String>, // TODO: remove?

// Acls
pub allowlist: Vec<Pubkey>,
pub blocklist: Vec<Pubkey>,
pub allowlist: Option<Vec<Pubkey>>, // TODO: optional
pub blocklist: Option<Vec<Pubkey>>, // TODO: optional

// Policies
pub lock_up_period_in_seconds: i32,
pub lock_up_period_in_seconds: Option<i32>, // TODO: optional
pub permanent_delegate: Option<Pubkey>,
pub default_account_state_frozen: bool,
pub default_account_state_frozen: Option<bool>, // TODO: optional

// Metadata
pub is_raw_openfunds: bool,
pub is_raw_openfunds: Option<bool>, // TODO: optional
pub raw_openfunds: Option<ShareClassOpenfundsModel>,
}

Expand Down
2 changes: 1 addition & 1 deletion anchor/programs/glam/src/state/model/openfunds.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ impl From<&ShareClassModel> for Vec<ShareClassField> {
fn from(model: &ShareClassModel) -> Self {
let mut res = vec![];
// Derived fields
let is_raw_openfunds = model.is_raw_openfunds;
let is_raw_openfunds = model.is_raw_openfunds.unwrap_or(false);
let model = model.clone();
if !is_raw_openfunds {
let v: Vec<(Option<String>, ShareClassFieldName)> = vec![
Expand Down
Loading

0 comments on commit a41158a

Please sign in to comment.