Security Considerations
Voltr Protocol - Security Considerations for Custom Adaptor Development
This guide outlines critical security considerations and best practices when developing custom adaptors for the Voltr Protocol.
Account Security
1. Authority Validation
// Always validate authorities and signers
require!(
ctx.accounts.vault_strategy_auth.key() == expected_auth,
AdaptorError::InvalidAccountOwner
);
// Verify protocol program ownership
require!(
ctx.accounts.protocol_program.key() == strategy.protocol_program,
AdaptorError::InvalidProtocolProgram
);
Key areas to validate:
Strategy authority signatures
Protocol program ownership
Token account authorities
Account derivation paths
PDA validation
2. Token Account Safety
// Example token account validation pattern
fn validate_token_accounts(
reserve_info: &AccountInfo,
vault_asset_mint: Pubkey,
reserve_collateral_mint_account: Pubkey,
user_destination_collateral: Pubkey,
vault_strategy_auth: Pubkey,
collateral_token_program: Pubkey
) -> Result<()> {
// Verify mints match expected values
require!(
reserve_liquidity_mint == vault_asset_mint.key(),
AdaptorError::InvalidReserveAccount
);
// Validate ATA derivation
require!(
get_associated_token_address_with_program_id(
&vault_strategy_auth.key(),
&reserve_collateral_mint_account.key(),
&collateral_token_program.key()
) == user_destination_collateral.key(),
AdaptorError::InvalidAccountInput
);
Ok(())
}
Critical checks:
Verify token mint associations
Validate token account authorities
Check ATA derivation
Enforce token program consistency
Verify token account state
3. PDA Derivation Security
// Example PDA derivation and validation
[strategy] = PublicKey.findProgramAddressSync(
[SEEDS.STRATEGY, counterparty_asset_ta.key().as_ref()],
DEFAULT_ADAPTOR_PROGRAM_ID
);
// Validate PDA derivation in accounts struct
#[account(
seeds = [constants::STRATEGY_SEED, strategy.load()?.counterparty_asset_ta.key().as_ref()],
bump = strategy.load()?.bump
)]
pub strategy: AccountLoader<'info, Strategy>,
Key considerations:
Use consistent seed ordering
Store and validate bumps
Check PDA ownership
Verify seed values
Validate authority PDAs
State Management Security
1. Position Value Tracking
fn calculate_current_amount(
collateral_amount: u64,
reserve_info: &AccountInfo
) -> Result<u64> {
// Always handle potential overflows
let total_liquidity = available_amount
.checked_add(borrowed_amount)
.ok_or(AdaptorError::MathOverflow)?;
// Use checked math operations
let liquidity_amount = (collateral_amount as u128)
.checked_mul(total_liquidity)
.ok_or(AdaptorError::MathOverflow)?
.checked_div(total_supply)
.ok_or(AdaptorError::MathOverflow)? as u64;
Ok(liquidity_amount)
}
Critical aspects:
Use checked math operations
Handle decimal precision
Track position changes atomically
Validate calculations
Handle underflow/overflow
2. State Updates
impl Strategy {
pub fn update_position(&mut self, value: u64) -> Result<()> {
// Validate new state
require!(value >= 0, AdaptorError::InvalidAmount);
// Update atomically
self.position_value = value;
self.last_updated_ts = Clock::get()?.unix_timestamp;
Ok(())
}
}
Best practices:
Atomic updates
State validation
Version tracking
State consistency checks
Timestamp validation
Protocol Integration Security
1. CPI Safety
// Example safe CPI pattern
fn perform_protocol_operation(
ctx: Context,
args: ProtocolArgs
) -> Result<()> {
// Validate CPI accounts
validate_protocol_accounts(ctx.remaining_accounts)?;
// Create CPI with correct signers
let cpi_ctx = CpiContext::new_with_signer(
ctx.accounts.protocol_program.to_account_info(),
protocol_accounts,
&[&authority_seeds]
);
// Perform CPI
protocol::cpi::operation(cpi_ctx, args)?;
Ok(())
}
Security measures:
Validate all CPI accounts
Sign with correct authority
Check return values
Handle CPI errors
Validate program IDs
2. Protocol State Validation
fn validate_protocol_state(
protocol_account: &AccountInfo,
expected_state: ProtocolState
) -> Result<()> {
let state = ProtocolState::try_from_slice(&protocol_account.data.borrow())?;
// Validate protocol constraints
require!(
state.is_valid_for_operation(),
AdaptorError::InvalidProtocolState
);
// Check protocol specific rules
validate_protocol_constraints(&state)?;
Ok(())
}
Key checks:
Verify protocol state
Validate constraints
Check protocol limits
Handle protocol errors
Validate protocol accounts
Error Handling
1. Comprehensive Error Types
#[error_code]
pub enum AdaptorError {
#[msg("Invalid amount provided.")]
InvalidAmount,
#[msg("Invalid account owner.")]
InvalidAccountOwner,
#[msg("Invalid token mint.")]
InvalidTokenMint,
#[msg("Math overflow.")]
MathOverflow,
#[msg("Invalid protocol state.")]
InvalidProtocolState,
// Protocol-specific errors
#[msg("Protocol constraint violated.")]
ProtocolConstraintViolation,
}
Error handling practices:
Descriptive error types
Protocol-specific errors
Clear error messages
Error propagation
State validation errors
2. Input Validation
fn validate_operation_inputs(
amount: u64,
args: &OperationArgs
) -> Result<()> {
// Amount validation
require!(amount > 0, AdaptorError::InvalidAmount);
require!(amount <= max_amount, AdaptorError::InvalidAmount);
// Args validation
if let Some(args) = args {
validate_args(args)?;
}
Ok(())
}
Important checks:
Validate all inputs
Check amount ranges
Verify arguments
Validate timestamps
Check protocol limits
Operational Security
1. Transaction Atomicity
// Example atomic operation
pub fn handle_protocol_operation(ctx: Context) -> Result<()> {
// 1. Validate pre-state
let pre_state = validate_pre_state(ctx)?;
// 2. Perform operation atomically
protocol_operation(ctx)?;
// 3. Validate post-state
validate_post_state(ctx, pre_state)?;
Ok(())
}
Key considerations:
Atomic operations
State validation
Transaction rollback
Error recovery
State consistency
2. Upgrade Safety
#[account(zero_copy(unsafe))]
pub struct Strategy {
pub version: u8,
// Add fields with padding for future upgrades
pub _reserved: [u8; 64],
}
Upgrade considerations:
Version tracking
State migration
Backward compatibility
Feature flags
Reserved space
Testing Requirements
Security Tests
Authority validation
Account validation
State consistency
Error handling
Edge cases
Integration Tests
Protocol interactions
State transitions
Error conditions
Upgrade paths
Multi-instruction scenarios
Fuzzing Tests
Input validation
State mutations
Account combinations
Error conditions
Protocol interactions
Security Checklist
Before deployment, verify:
Account Security
State Management
Protocol Integration
Testing
Last updated