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

Propeller audit #422

Draft
wants to merge 17 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
1 change: 1 addition & 0 deletions packages/solana-contracts/Anchor.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ initialize_pool = "yarn run ts-node scripts/initializePool.ts"
initialize_propeller = "yarn run ts-node scripts/initializePropeller.ts"
scratch = "yarn run ts-node scripts/scratch.ts"
add_to_pool = "yarn run ts-node scripts/addToPool.ts"
parse_account = "yarn run ts-node scripts/parseAccount.ts"

[test]
startup_wait = 100000
Expand Down
53 changes: 3 additions & 50 deletions packages/solana-contracts/programs/propeller/src/constants.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,15 @@
use {num_traits::FromPrimitive, rust_decimal::Decimal};
use {rust_decimal::Decimal};

pub const CURRENT_SWIM_PAYLOAD_VERSION: u8 = 1;
pub const TOKEN_COUNT: usize = 2;
pub const ENACT_DELAY: i64 = 3 * 86400;

// seed prefixes
// pub const SEED_PREFIX_CUSTODIAN: &str = "icco-custodian";
// pub const SEED_PREFIX_SALE: &str = "icco-sale";
// pub const SEED_PREFIX_BUYER: &str = "icco-buyer";

pub const CHAIN_ID_SOL: u16 = 1;
pub const CHAIN_ID_ETH: u16 = 2;

pub const TOKEN_BRIDGE_MINT_OUTPUT_TOKEN_INDEX: usize = 0;
pub const SWAP_EXACT_INPUT_OUTPUT_TOKEN_INDEX: u8 = 0;
pub const METAPOOL_SWIM_USD_INDEX: u8 = 0;
pub const REMOVE_EXACT_BURN_OUTPUT_TOKEN_INDEX: u8 = 0;
pub const SWAP_EXACT_OUTPUT_INPUT_TOKEN_INDEX: u8 = 1;
/// Pool IXs for sending ignore slippage
pub const PROPELLER_MINIMUM_OUTPUT_AMOUNT: u64 = 0u64;

// pub const LAMPORTS_PER_SOL_DECIMAL: Decimal = Decimal::from_u64(1_000_000_000u64).unwrap();
pub const LAMPORTS_PER_SOL_DECIMAL: Decimal = Decimal::from_parts(1_000_000_000u32, 0, 0, false, 0u32);

#[cfg(test)]
Expand All @@ -33,41 +24,3 @@ mod test {
// println!("token_bridge_id2: {}", ID);
}
}
// // vaa payload types
// pub const PAYLOAD_SALE_INIT_SOLANA: u8 = 5; // 1 for everyone else
// pub const PAYLOAD_ATTEST_CONTRIBUTIONS: u8 = 2;
// pub const PAYLOAD_SALE_SEALED: u8 = 3;
// pub const PAYLOAD_SALE_ABORTED: u8 = 4;
//
// // universal
// pub const PAYLOAD_HEADER_LEN: usize = 33; // payload + sale id
// pub const INDEX_SALE_ID: usize = 1;
//
// // for sale init
// pub const INDEX_SALE_INIT_TOKEN_ADDRESS: usize = 33;
// pub const INDEX_SALE_INIT_TOKEN_CHAIN: usize = 65;
// pub const INDEX_SALE_INIT_TOKEN_DECIMALS: usize = 67;
// pub const INDEX_SALE_INIT_SALE_START: usize = 68;
// pub const INDEX_SALE_INIT_SALE_END: usize = 100;
// pub const INDEX_SALE_INIT_ACCEPTED_TOKENS_START: usize = 132;
//
// pub const ACCEPTED_TOKEN_NUM_BYTES: usize = 33;
// pub const ACCEPTED_TOKENS_MAX: usize = 8;
// pub const INDEX_ACCEPTED_TOKEN_INDEX: usize = 0;
// pub const INDEX_ACCEPTED_TOKEN_ADDRESS: usize = 1;
// pub const INDEX_ACCEPTED_TOKEN_END: usize = 33;
//
// // for attest contributions
// pub const ATTEST_CONTRIBUTIONS_ELEMENT_LEN: usize = 33; // token index + amount
//
// // for sale sealed
// pub const INDEX_SALE_SEALED_ALLOCATIONS_START: usize = 33;
//
// pub const ALLOCATION_NUM_BYTES: usize = 65;
// pub const INDEX_ALLOCATIONS_AMOUNT: usize = 1;
// pub const INDEX_ALLOCATIONS_EXCESS: usize = 33;
// pub const INDEX_ALLOCATIONS_END: usize = 65;

// misc
// pub const PAD_U8: usize = 31;
// pub const PAD_U64: usize = 24;
77 changes: 61 additions & 16 deletions packages/solana-contracts/programs/propeller/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ pub enum PropellerError {
#[msg("TransferNotAllowed")]
TransferNotAllowed,

#[msg("Invalid Pool for Init To SwimUSD")]
InvalidPoolForInitToSwimUsd,

#[msg("Incorrect ProgramId for CPI return value")]
InvalidCpiReturnProgramId,

Expand Down Expand Up @@ -72,7 +75,7 @@ pub enum PropellerError {
#[msg("Not a valid Switchboard account")]
InvalidSwitchboardAccount,

#[msg("Switchboard feed has not been updated in 5 minutes")]
#[msg("Switchboard feed value is stale ")]
StaleFeed,

#[msg("Switchboard feed exceeded provided confidence interval")]
Expand All @@ -81,33 +84,42 @@ pub enum PropellerError {
#[msg("Insufficient Amount being transferred")]
InsufficientAmount,

#[msg("Invalid Wormhole Claim Account")]
InvalidWormholeClaimAccount,

#[msg("Invalid claim data")]
InvalidClaimData,

#[msg("Claim Account not claimed")]
ClaimNotClaimed,

#[msg("Invalid Propeller Admin")]
InvalidPropellerAdmin,
#[msg("Invalid Propeller GovernanceKey")]
InvalidPropellerGovernanceKey,

#[msg("Invalid Propeller Pause Key")]
InvalidPropellerPauseKey,

#[msg("Invalid Pool for Token Id Map")]
InvalidTokenIdMapPool,
#[msg("Invalid Pool for Token Number Map")]
InvalidTokenNumberMapPool,

#[msg("Invalid Output Token Index")]
InvalidOutputTokenIndex,

#[msg("Invalid Pool Token Index for Token Id Map")]
InvalidTokenIdMapPoolTokenIndex,
#[msg("Invalid Pool Token Index for Token Number Map")]
InvalidTokenNumberMapPoolTokenIndex,

#[msg("Invalid Pool Token Mint for Token Id Map")]
InvalidTokenIdMapPoolTokenMint,
#[msg("Invalid Pool Token Mint for Token Number Map")]
InvalidTokenNumberMapPoolTokenMint,

#[msg("Invalid Pool Ix for Token Id Map")]
InvalidTokenIdMapPoolIx,
#[msg("Invalid To Token Step for Token Number Map")]
InvalidTokenNumberMapToTokenStep,

#[msg("Invalid Gas Kickstart parameter in Swim Payload")]
InvalidSwimPayloadGasKickstart,

#[msg("Invalid Marginal Price Pool")]
InvalidMarginalPricePool,

#[msg("Invalid Marginal Price Pool Accounts")]
InvalidMarginalPricePoolAccounts,

Expand Down Expand Up @@ -141,15 +153,48 @@ pub enum PropellerError {
#[msg("Owner of token account != swimPayload.owner")]
IncorrectOwnerForCreateTokenAccount,

#[msg("TokenIdMap exists. Please use the correct instruction")]
TokenIdMapExists,

#[msg("Invalid address for TokenIdMap account")]
InvalidTokenIdMapAccountAddress,
#[msg("TokenNumberMap exists. Please use the correct instruction")]
TokenNumberMapExists,

#[msg("Invalid Swim Payload version")]
InvalidSwimPayloadVersion,

#[msg("Invalid Aggregator")]
InvalidAggregator,

#[msg("Invalid Fee Vault")]
InvalidFeeVault,

#[msg("Invalid Memo")]
InvalidMemo,

#[msg("ToTokenNumber does not match SwimPayload.to_tokenNumber")]
ToTokenNumberMismatch,

#[msg("Routing Contract is paused")]
IsPaused,

#[msg("Target Chain is paused")]
TargetChainIsPaused,

#[msg("Invalid Target Chain Map")]
InvalidTargetChainMap,

#[msg("Invalid SwimPayloadMessagePayer")]
InvalidSwimPayloadMessagePayer,

#[msg("Invalid New Pause Key for UpdatePauseKey")]
InvalidNewPauseKey,

#[msg("Invalid Upcoming Governance Key for Prepare Governance Transition")]
InvalidUpcomingGovernanceKey,

#[msg("Invalid Enact Governance Transition")]
InvalidEnact,

#[msg("Insufficient Delay for Enact Governance Transition")]
InsufficientDelay,

#[msg("Invalid Fee Tracker")]
InvalidFeeTracker,
}
131 changes: 131 additions & 0 deletions packages/solana-contracts/programs/propeller/src/fees.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
use {
crate::{constants::LAMPORTS_PER_SOL_DECIMAL, marginal_price_pool::*, FeeTracker, Propeller, PropellerError},
anchor_lang::prelude::*,
anchor_spl::token::TokenAccount,
num_traits::{FromPrimitive, ToPrimitive},
rust_decimal::Decimal,
switchboard_v2::{AggregatorAccountData, SwitchboardDecimal, SWITCHBOARD_PROGRAM_ID},
};

pub trait Fees<'info> {
/// Calculate the fees, including txn and rent exemptions, in lamports.
fn calculate_fees_in_lamports(&self) -> Result<u64>;
// fn get_marginal_prices(&self) -> Result<[BorshDecimal; TOKEN_COUNT]>;
// fn convert_fees_to_swim_usd_atomic(&self, fee_in_lamports: u64) -> Result<u64>;
fn transfer_fees_to_vault(&mut self, fees_in_swim_usd: u64) -> Result<()>;
}

#[derive(Accounts)]
pub struct FeeTracking<'info> {
#[account(
constraint =
*aggregator.to_account_info().owner == SWITCHBOARD_PROGRAM_ID @ PropellerError::InvalidSwitchboardAccount,
)]
pub aggregator: AccountLoader<'info, AggregatorAccountData>,

#[account(mut)]
/// this is "to_fees"
/// recipient of fees for executing complete transfer (e.g. relayer)
pub fee_vault: Box<Account<'info, TokenAccount>>,

#[account(mut)]
pub fee_tracker: Box<Account<'info, FeeTracker>>,
pub marginal_price_pool: MarginalPricePool<'info>,
}

impl<'info> FeeTracking<'info> {
pub fn validate(&self, propeller: &Propeller, payer: &Pubkey, program_id: &Pubkey) -> Result<()> {
require_keys_eq!(
self.marginal_price_pool.pool.key(),
propeller.marginal_price_pool,
PropellerError::InvalidMarginalPricePool
);
require_keys_eq!(self.fee_vault.key(), propeller.fee_vault, PropellerError::InvalidFeeVault);
require_keys_eq!(self.aggregator.key(), propeller.aggregator, PropellerError::InvalidAggregator);
let expected_fee_tracker = Pubkey::create_program_address(
&[
b"propeller".as_ref(),
b"fee".as_ref(),
propeller.swim_usd_mint.as_ref(),
payer.as_ref(),
&[self.fee_tracker.bump],
],
program_id,
)
.map_err(|_| PropellerError::InvalidFeeTracker)?;
require_keys_eq!(self.fee_tracker.key(), expected_fee_tracker, PropellerError::InvalidFeeTracker);
msg!("finished fees tracking validation");
self.marginal_price_pool.validate(propeller)
}

pub fn two_pool_program_key(&self) -> Pubkey {
self.marginal_price_pool.two_pool_program.key()
}

pub fn track_fees(&mut self, fees_in_lamports: u64, propeller: &Propeller) -> Result<u64> {
let fee_in_swim_usd_atomic = self.convert_fees_to_swim_usd_atomic(fees_in_lamports, propeller)?;
self.update_fee_tracker(fee_in_swim_usd_atomic)
}

fn convert_fees_to_swim_usd_atomic(&self, fee_in_lamports: u64, propeller: &Propeller) -> Result<u64> {
let intermediate_token_price_decimal: Decimal =
self.marginal_price_pool.get_marginal_price_decimal(propeller)?;

msg!("intermediate_token_price_decimal: {:?}", intermediate_token_price_decimal);

let fee_in_lamports_decimal = Decimal::from_u64(fee_in_lamports).ok_or(PropellerError::ConversionError)?;
msg!("fee_in_lamports(u64): {:?} fee_in_lamports_decimal: {:?}", fee_in_lamports, fee_in_lamports_decimal);

let lamports_intermediate_token_price = self.get_lamports_intermediate_token_price(propeller)?;
let fee_in_swim_usd_decimal = lamports_intermediate_token_price
.checked_mul(fee_in_lamports_decimal)
.and_then(|x| x.checked_div(intermediate_token_price_decimal))
.ok_or(PropellerError::IntegerOverflow)?;

// let swim_usd_decimals =
// get_swim_usd_mint_decimals(&swim_usd_mint_key, &self.marginal_price_pool, &marginal_price_pool_lp_mint)?;
let swim_usd_decimals = self.marginal_price_pool.get_swim_usd_mint_decimals(propeller)?;
msg!("swim_usd_decimals: {:?}", swim_usd_decimals);

let ten_pow_decimals =
Decimal::from_u64(10u64.pow(swim_usd_decimals as u32)).ok_or(PropellerError::IntegerOverflow)?;
let fee_in_swim_usd_atomic = fee_in_swim_usd_decimal
.checked_mul(ten_pow_decimals)
.and_then(|v| v.to_u64())
.ok_or(PropellerError::ConversionError)?;

msg!(
"fee_in_swim_usd_decimal: {:?} fee_in_swim_usd_atomic: {:?}",
fee_in_swim_usd_decimal,
fee_in_swim_usd_atomic
);
Ok(fee_in_swim_usd_atomic)
}

fn get_lamports_intermediate_token_price(&self, propeller: &Propeller) -> Result<Decimal> {
let feed = self.aggregator.load()?;
feed.check_staleness(Clock::get().unwrap().unix_timestamp, propeller.max_staleness)
.map_err(|_| error!(PropellerError::StaleFeed))?;

// check feed does not exceed max_confidence_interval
// let max_confidence_interval = f64::MAX;
// // let max_confidence_interval = propeller.max_confidence_interval;
// feed.check_confidence_interval(SwitchboardDecimal::from_f64(max_confidence_interval))
// .map_err(|_| error!(PropellerError::ConfidenceIntervalExceeded))?;

let sol_usd_price: Decimal = feed.get_result()?.try_into()?;

let lamports_usd_price =
sol_usd_price.checked_div(LAMPORTS_PER_SOL_DECIMAL).ok_or(PropellerError::IntegerOverflow)?;
msg!("sol_usd_price:{},lamports_usd_price: {}", sol_usd_price, lamports_usd_price);
Ok(lamports_usd_price)
// check whether the feed has been updated in the last 300 seconds
}

fn update_fee_tracker(&mut self, fees_in_swim_usd_atomic: u64) -> Result<u64> {
let fee_tracker = &mut self.fee_tracker;
fee_tracker.fees_owed =
fee_tracker.fees_owed.checked_add(fees_in_swim_usd_atomic).ok_or(PropellerError::IntegerOverflow)?;
Ok(fees_in_swim_usd_atomic)
}
}
Loading