Skip to content
Merged
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: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ This Anchor program enables users to migrate their tokens from an old token mint

- Node.js and Yarn
- Rust and Cargo
- Solana CLI tools
- Anchor CLI
- Solana CLI (v2.3.0) tools
- Anchor CLI (v0.31.1)

### Installation

Expand Down
8 changes: 3 additions & 5 deletions programs/token-migrator/src/contexts/initialize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,7 @@ use crate::state::{Strategy, Vault};
#[derive(Accounts)]
#[instruction(mint_from: Pubkey, mint_to: Pubkey)]
pub struct Initialize<'info> {
#[account(
mut,
// ℹ️ NOTE: Remove `address` constraint to make contract permissionless.
address = pubkey!("ELT1uRmtFvYP6WSrc4mCZaW7VVbcdkcKAj39aHSVCmwH")
)]
#[account(mut)]
admin: Signer<'info>,

// Ensure our vaults were initialized in preInstructions
Expand Down Expand Up @@ -45,6 +41,8 @@ impl<'info> Initialize<'info> {
strategy: Strategy,
bump: [u8; 1],
) -> Result<()> {
strategy.validate()?;

self.vault.set_inner(Vault {
admin: self.admin.key(),
mint_from,
Expand Down
2 changes: 1 addition & 1 deletion programs/token-migrator/src/contexts/migrate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ impl<'info> Migrate<'info> {
/// # Supply From
///
/// Calculates supply of `from` token to simulate a token burn. This is calculated by:
/// ```
/// ```ignore
/// let supply_from = mint_from.supply - vault_from_ata.amount
/// ```
#[inline(always)]
Expand Down
7 changes: 7 additions & 0 deletions programs/token-migrator/src/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use anchor_lang::prelude::*;

#[error_code]
pub enum MigratorError {
#[msg("Fixed strategy exponent out of range (|e| must be <= 19)")]
ExponentOutOfRange,
}
2 changes: 2 additions & 0 deletions programs/token-migrator/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ pub use state::*;

pub mod events;

pub mod errors;

#[cfg(not(feature = "no-entrypoint"))]
use solana_security_txt::security_txt;

Expand Down
45 changes: 45 additions & 0 deletions programs/token-migrator/src/state.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use anchor_lang::prelude::*;

use crate::errors::MigratorError;

#[derive(AnchorSerialize, AnchorDeserialize, Clone, Copy, PartialEq, Eq, InitSpace)]
/// # Strategy
///
Expand All @@ -12,7 +14,30 @@ pub enum Strategy {
Fixed { e: i8 },
}

/// Maximum magnitude of the `Fixed` exponent. `10^19` is the largest power of
/// ten that fits in a `u64` (`10^20` overflows), so bounding `|e| <= 19` keeps
/// the `10u64.pow(|e|)` scaling in `withdraw_amount` from overflowing and rules
/// out the `i8::MIN` negation panic.
pub const MAX_FIXED_EXPONENT: u8 = 19;

impl Strategy {
/// # Validate
/// Ensures the strategy parameters are within safe bounds before the vault
/// is persisted. For `Fixed`, the exponent magnitude must be
/// `<= MAX_FIXED_EXPONENT` so the `10u64.pow(|e|)` scaling in
/// `withdraw_amount` cannot overflow. `ProRata` carries no parameters and is
/// always valid.
pub fn validate(&self) -> Result<()> {
if let Strategy::Fixed { e } = self {
require!(
e.unsigned_abs() <= MAX_FIXED_EXPONENT,
MigratorError::ExponentOutOfRange
);
}

Ok(())
}

/// # Withdraw Amount
/// Calculates the amount of tokens the user can withdraw based upon the `Strategy` implemented.
#[inline(always)]
Expand Down Expand Up @@ -94,4 +119,24 @@ mod tests {
let strategy = Strategy::ProRata;
assert_eq!(strategy.withdraw_amount(10, 100, 100).unwrap(), 10);
}

#[test]
fn test_validate_pro_rata_ok() {
assert!(Strategy::ProRata.validate().is_ok());
}

#[test]
fn test_validate_fixed_within_bounds_ok() {
assert!(Strategy::Fixed { e: 0 }.validate().is_ok());
assert!(Strategy::Fixed { e: 19 }.validate().is_ok());
assert!(Strategy::Fixed { e: -19 }.validate().is_ok());
}

#[test]
fn test_validate_fixed_out_of_bounds_err() {
assert!(Strategy::Fixed { e: 20 }.validate().is_err());
assert!(Strategy::Fixed { e: -20 }.validate().is_err());
assert!(Strategy::Fixed { e: i8::MAX }.validate().is_err());
assert!(Strategy::Fixed { e: i8::MIN }.validate().is_err());
}
}