Skip to content
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