Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added documentation and used transfer_checked instead #254

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions programs/conditional_vault/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ pub enum VaultError {
InsufficientConditionalTokens,
#[msg("This `vault_underlying_token_account` is not this vault's `underlying_token_account`")]
InvalidVaultUnderlyingTokenAccount,
#[msg("This `vault_underlying_token_mint` is not this vault's `underlying_token_mint`")]
InvalidVaultUnderlyingTokenMint,
#[msg("This conditional token mint is not this vault's conditional token mint")]
InvalidConditionalTokenMint,
#[msg("Question needs to be resolved before users can redeem conditional tokens for underlying tokens")]
Expand All @@ -28,6 +30,8 @@ pub enum VaultError {
BadConditionalTokenAccount,
#[msg("User conditional token account mint does not match conditional mint")]
ConditionalTokenMintMismatch,
#[msg("Vault or user token account mint does not match underlying token mint")]
UnderlyingTokenMintMismatch,
#[msg("Payouts must sum to 1 or more")]
PayoutZero,
#[msg("Question already resolved")]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,50 +1,92 @@
use super::*;

/// Module containing proph3t's public key
/// This is used for authorization in production
pub mod proph3t_deployer {
use anchor_lang::declare_id;

declare_id!("HfFi634cyurmVVDr9frwu4MjGLJzz9XbAJz981HdVaNz");
}

/// Arguments required for adding metadata to conditional tokens.
/// This metadata helps identify and display token information in wallets and GUIs.
#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct AddMetadataToConditionalTokensArgs {
/// The display name of the conditional token
/// Example: "YES_SOL_ABOVE_50K_DEC2024"
pub name: String,

/// The trading symbol for the conditional token
/// Example: "YSOL50K"
pub symbol: String,

/// URI pointing to the token's metadata JSON
/// This should contain extended information about the token,
/// such as description, images, and additional attributes
pub uri: String,
}

#[event_cpi]
#[derive(Accounts)]
pub struct AddMetadataToConditionalTokens<'info> {
/// The account paying for the metadata account creation
/// In production, this must be proph3t - the authorized deployer
#[account(mut)]
pub payer: Signer<'info>,

/// The vault account that controls the conditional tokens
/// Must be in Active status to modify metadata
#[account(mut)]
pub vault: Account<'info, ConditionalVault>,

/// The mint account of the conditional token
/// Must have the vault as its mint authority
#[account(
mut,
mint::authority = vault,
)]
pub conditional_token_mint: Account<'info, Mint>,

/// The account that will store the token metadata
/// Must be empty before initialization
/// CHECK: verified via cpi into token metadata
#[account(mut)]
pub conditional_token_metadata: AccountInfo<'info>,

/// The Token Metadata Program that will create and store the metadata
pub token_metadata_program: Program<'info, Metadata>,

/// The System Program Account
pub system_program: Program<'info, System>,

/// The Rent Sysvar account for rent calculations
pub rent: Sysvar<'info, Rent>,
}

impl AddMetadataToConditionalTokens<'_> {
/// Validates the preconditions for adding metadata to conditional tokens
///
/// # Checks
/// * Verifies the metadata account is empty (prevents overwriting)
/// * In production, ensures only proph3t (the authorized deployer) can add metadata
///
/// # Returns
/// * `Ok(())` if all validations pass
/// * `Err(VaultError::ConditionalTokenMetadataAlreadySet)` if metadata exists
pub fn validate(&self) -> Result<()> {
// Commented out for reference:
// require!(
// self.vault.status == VaultStatus::Active,
// VaultError::VaultAlreadySettled
// );

// Ensure we're not overwriting existing metadata
require!(
self.conditional_token_metadata.data_is_empty(),
VaultError::ConditionalTokenMetadataAlreadySet
);

// In production builds, restrict access to authorized deployer
#[cfg(feature = "production")]
require_eq!(
self.payer.key(), proph3t_deployer::ID
Expand All @@ -53,12 +95,36 @@ impl AddMetadataToConditionalTokens<'_> {
Ok(())
}

/// Handles the addition of metadata to conditional tokens
///
/// # Arguments
/// * `ctx` - The context containing all required accounts
/// * `args` - The metadata arguments (name, symbol, URI)
///
/// # Steps
/// 1. Generates vault PDA seeds for signing
/// 2. Creates metadata account via CPI to Token Metadata Program
/// 3. Increments vault sequence number
/// 4. Emits event with metadata details
///
/// # Example
/// ```ignore
/// let args = AddMetadataToConditionalTokensArgs {
/// name: "YES_SOL_ABOVE_50K".to_string(),
/// symbol: "YBTC50K".to_string(),
/// uri: "https://metadao.fi/token/123".to_string(),
/// };
/// add_metadata_to_conditional_tokens(ctx, args)?;
/// ```
pub fn handle(ctx: Context<Self>, args: AddMetadataToConditionalTokensArgs) -> Result<()> {
// Generate vault PDA seeds for signing metadata transactions
let seeds = generate_vault_seeds!(ctx.accounts.vault);
let signer_seeds = &[&seeds[..]];

// Prepare CPI to token metadata program
let cpi_program = ctx.accounts.token_metadata_program.to_account_info();

// Setup accounts for metadata creation
let cpi_accounts = CreateMetadataAccountsV3 {
metadata: ctx.accounts.conditional_token_metadata.to_account_info(),
mint: ctx.accounts.conditional_token_mint.to_account_info(),
Expand All @@ -69,24 +135,27 @@ impl AddMetadataToConditionalTokens<'_> {
rent: ctx.accounts.rent.to_account_info(),
};

// Create metadata account with provided details
create_metadata_accounts_v3(
CpiContext::new(cpi_program, cpi_accounts).with_signer(signer_seeds),
DataV2 {
name: args.name.clone(),
symbol: args.symbol.clone(),
uri: args.uri.clone(),
seller_fee_basis_points: 0,
creators: None,
collection: None,
uses: None,
seller_fee_basis_points: 0, // No fees for conditional tokens
creators: None, // No creator royalties
collection: None, // Not part of a collection
uses: None, // No uses metadata
},
false,
true,
None,
false, // Is mutable
true, // Update authority is signer
None, // Collection details
)?;

// Increment vault sequence number for tracking
ctx.accounts.vault.seq_num += 1;

// Emit event for indexing and tracking
let clock = Clock::get()?;
emit_cpi!(AddMetadataToConditionalTokensEvent {
common: CommonFields {
Expand Down
102 changes: 101 additions & 1 deletion programs/conditional_vault/src/instructions/common.rs
Original file line number Diff line number Diff line change
@@ -1,39 +1,134 @@
use super::*;

/// Account context for interacting with a conditional vault.
/// This structure validates and processes all accounts needed for vault operations
/// such as minting, burning, or settling conditional tokens.
#[event_cpi]
#[derive(Accounts)]
pub struct InteractWithVault<'info> {
/// The non-mutable question account associated with this vault
/// Contains outcome information and oracle details
pub question: Account<'info, Question>,

/// The vault account being interacted with
/// Must be associated with the provided question account
/// Marked as mutable to allow updating vault state
#[account(mut, has_one = question)]
pub vault: Account<'info, ConditionalVault>,

/// The mint account for the underlying token used in this vault.
/// This account is critical for token validation and decimal precision checking
/// during transfer operations.
///
/// # Validation
/// - Must match the mint stored in vault account (`vault.underlying_token_mint`)
/// - Must be a valid SPL token mint account
/// - Used to verify decimal precision in token operations
///
/// # Security Considerations
/// 1. Address validation ensures we're using the correct underlying token
/// 2. Prevents token substitution attacks
/// 3. Enables decimal verification in transfer_checked operations
/// 4. Guards against incorrect token account associations
///
/// # Usage in Operations
/// - Transfer validation: Used in transfer_checked for decimal verification
/// - Decimal consistency: Ensures all operations maintain correct precision
/// - Token validation: Verifies token account relationships
///
/// # Example
/// ```ignore
/// token::transfer_checked(
/// ctx,
/// amount,
/// vault_underlying_token_mint.decimals, // Decimal verification
/// )?;
/// ```
///
/// # Error Scenarios
/// Returns `VaultError::UnderlyingTokenMintMismatch` when:
/// - Provided mint doesn't match vault's recorded mint
/// - Incorrect mint account is supplied
/// - Mint address validation fails
#[account(address = vault.underlying_token_mint @ VaultError::InvalidVaultUnderlyingTokenMint)]
pub vault_underlying_token_mint: Account<'info, Mint>,

/// The vault's underlying token account
/// Holds the collateral tokens backing the conditional tokens
/// Must match the account stored in the vault's state
#[account(
mut,
constraint = vault_underlying_token_account.key() == vault.underlying_token_account @ VaultError::InvalidVaultUnderlyingTokenAccount
)]
pub vault_underlying_token_account: Account<'info, TokenAccount>,

/// The authority (user) initiating the interaction
/// Must sign the transaction to authorize token transfers
pub authority: Signer<'info>,

/// The user's token account for the underlying token
/// Used for depositing/withdrawing underlying tokens/collateral
/// Must be owned by the authority and match the vault's underlying token mint
#[account(
mut,
token::authority = authority,
token::mint = vault.underlying_token_mint
)]
pub user_underlying_token_account: Account<'info, TokenAccount>,

/// The SPL Token program account
/// Required for token operations
pub token_program: Program<'info, Token>,
}

impl<'info, 'c: 'info> InteractWithVault<'info> {
/// Retrieves and validates conditional token mints and their corresponding user accounts
/// from the remaining accounts in the transaction.
///
/// # Arguments
/// * `ctx` - The context containing all transaction accounts including remaining accounts
///
/// # Returns
/// * `Result<(Vec<Account<'info, Mint>>, Vec<Account<'info, TokenAccount>>)>`:
/// - First vector contains validated conditional token mint accounts
/// - Second vector contains validated user token accounts for each conditional token
///
/// # Validation Steps
/// 1. Verifies correct number of remaining accounts (2 accounts per outcome)
/// 2. Validates conditional token mint addresses match vault's records
/// 3. Ensures all mint accounts are valid
/// 4. Verifies user token accounts correspond to correct mints
///
/// # Errors
/// * `VaultError::InvalidConditionals` - Incorrect number of remaining accounts
/// * `VaultError::ConditionalMintMismatch` - Mint address doesn't match vault records
/// * `VaultError::BadConditionalMint` - Invalid mint account
/// * `VaultError::BadConditionalTokenAccount` - Invalid token account
/// * `VaultError::ConditionalTokenMintMismatch` - Token account mint mismatch
///
/// # Example
/// ```ignore
/// let (mints, user_accounts) = InteractWithVault::get_mints_and_user_token_accounts(&ctx)?;
/// // mints[0] is the mint for first outcome
/// // user_accounts[0] is user's token account for first outcome
/// ```
pub fn get_mints_and_user_token_accounts(
ctx: &Context<'_, '_, 'c, 'info, Self>,
) -> Result<(Vec<Account<'info, Mint>>, Vec<Account<'info, TokenAccount>>)> {
// Get iterator over remaining accounts
let remaining_accs = &mut ctx.remaining_accounts.iter();

// Calculate expected number of accounts based on outcomes
let expected_num_conditional_tokens = ctx.accounts.question.num_outcomes();

// Verify we have correct number of accounts (2 per outcome: mint + token account)
require_eq!(
remaining_accs.len(),
expected_num_conditional_tokens * 2,
VaultError::InvalidConditionals
);

// Initialize vectors to store validated accounts
let mut conditional_token_mints = vec![];
let mut user_conditional_token_accounts = vec![];

Expand All @@ -45,20 +140,25 @@ impl<'info, 'c: 'info> InteractWithVault<'info> {
VaultError::ConditionalMintMismatch
);

// really, this should never fail because we initialize mints when we initialize the vault
// Validate and convert to Mint account
// Note: This should never fail as mints are initialized with vault
conditional_token_mints.push(
Account::<Mint>::try_from(conditional_token_mint)
.or(Err(VaultError::BadConditionalMint))?,
);
}

// Second pass: validate and collect all user token accounts
for i in 0..expected_num_conditional_tokens {
// Get next account info
let user_conditional_token_account = next_account_info(remaining_accs)?;

// Validate and convert to TokenAccount
let user_conditional_token_account =
Account::<TokenAccount>::try_from(user_conditional_token_account)
.or(Err(VaultError::BadConditionalTokenAccount))?;

// Verify token account's mint matches corresponding conditional token mint
require_eq!(
user_conditional_token_account.mint,
conditional_token_mints[i].key(),
Expand Down
Loading
Loading