From bcda92c5d9f00cf870d031d61a56a2ba70c806f7 Mon Sep 17 00:00:00 2001 From: makarychev <3645723+makarychev@users.noreply.github.com> Date: Sun, 9 Jun 2024 22:56:30 +0300 Subject: [PATCH] add create donation v2 with multi currency support --- .../src/instructions/create_donation.rs | 1 + .../src/instructions/create_donation_v2.rs | 121 ++++++++++++++++++ programs/donaproto/src/instructions/mod.rs | 2 + programs/donaproto/src/lib.rs | 11 ++ .../donaproto/src/states/donation_data.rs | 3 + programs/donaproto/src/utils.rs | 59 +++++++++ 6 files changed, 197 insertions(+) create mode 100644 programs/donaproto/src/instructions/create_donation_v2.rs create mode 100644 programs/donaproto/src/utils.rs diff --git a/programs/donaproto/src/instructions/create_donation.rs b/programs/donaproto/src/instructions/create_donation.rs index ef41872..c496485 100644 --- a/programs/donaproto/src/instructions/create_donation.rs +++ b/programs/donaproto/src/instructions/create_donation.rs @@ -77,6 +77,7 @@ pub fn create_donation( donation_data.recipient = ctx.accounts.recipient.key(); donation_data.creator_data = ctx.accounts.creator_data.key(); donation_data.donation_protocol = ctx.accounts.donation_protocol.key(); + donation_data.donation_mint = ctx.accounts.donation_mint.key(); donation_data.holding_wallet = ctx.accounts.holding_wallet.key(); donation_data.holding_bump = holding_bump; donation_data.ipfs_hash = ipfs_hash; diff --git a/programs/donaproto/src/instructions/create_donation_v2.rs b/programs/donaproto/src/instructions/create_donation_v2.rs new file mode 100644 index 0000000..d73327f --- /dev/null +++ b/programs/donaproto/src/instructions/create_donation_v2.rs @@ -0,0 +1,121 @@ +use anchor_lang::prelude::*; +use anchor_spl::token::{Mint, TokenAccount}; +use raydium_amm_v3::states::PoolState; + +use crate::{ + errors::DonationError, + states::{AuthorizedClmmPool, CreatorData, DonationData, DonationProtocolData, MAX_IPFS_HASH_LEN}, + utils::calculate_amount, AUTHORIZED_CLMM_POOL_PREFIX, CREATOR_PREFIX, HOLDING_PREFIX, +}; + +#[derive(Accounts)] +pub struct CreateDonationV2<'info> { + #[account(init, payer = creator_wallet_address, space = DonationData::LEN)] + pub donation_data: Account<'info, DonationData>, + #[account( + constraint = donation_protocol.donation_mint == default_donation_mint.key(), + )] + pub donation_protocol: Account<'info, DonationProtocolData>, + + #[account( + constraint = holding_wallet.owner == *holding_wallet_owner.key, + constraint = holding_wallet.mint == donation_mint.key(), + )] + pub holding_wallet: Account<'info, TokenAccount>, + #[account( + seeds = [ + HOLDING_PREFIX.as_bytes(), + donation_data.to_account_info().key.as_ref(), + ], + bump, + )] + /// CHECK: pda account ["holding", donation_data] + holding_wallet_owner: AccountInfo<'info>, + + #[account( + constraint = recipient.mint == donation_mint.key(), + )] + pub recipient: Account<'info, TokenAccount>, + + #[account(mut, + constraint = creator_data.donation_protocol.key() == donation_protocol.key(), + seeds = [ + CREATOR_PREFIX.as_bytes(), + donation_protocol.to_account_info().key.as_ref(), + creator_wallet_address.key().as_ref(), + ], + bump, + )] + pub creator_data: Account<'info, CreatorData>, + pub donation_mint: Account<'info, Mint>, + pub default_donation_mint: Account<'info, Mint>, + #[account( + seeds = [ + AUTHORIZED_CLMM_POOL_PREFIX.as_bytes(), + donation_protocol.key().as_ref(), + pool_state.key().as_ref(), + ], + bump, + constraint = authorized_clmm_pool.donation_protocol == donation_protocol.key(), + constraint = authorized_clmm_pool.pool_state == pool_state.key(), + )] + pub authorized_clmm_pool: Account<'info, AuthorizedClmmPool>, + #[account( + constraint = (pool_state.load()?.token_mint_0 == donation_protocol.donation_mint && pool_state.load()?.token_mint_1 == donation_mint.key()) + || (pool_state.load()?.token_mint_1 == donation_protocol.donation_mint && pool_state.load()?.token_mint_0 == donation_mint.key()), + )] + pub pool_state: AccountLoader<'info, PoolState>, + #[account(mut)] + pub creator_wallet_address: Signer<'info>, + pub system_program: Program<'info, System>, + pub rent: Sysvar<'info, Rent> +} + +pub fn create_donation_v2( + ctx: Context, + amount: u64, + ipfs_hash: String, + ending_timestamp: u64, + holding_bump: u8, +) -> Result<()> { + let now_timestamp = Clock::get().expect("Time error").unix_timestamp as u64; + if ending_timestamp <= now_timestamp { + return Err(DonationError::InvalidEndingTimestamp.into()); + } + if ipfs_hash.len() > MAX_IPFS_HASH_LEN { + return Err(DonationError::IpfsHashTooLong.into()); + } + if amount < ctx.accounts.donation_protocol.min_amount_to_collect { + return Err(DonationError::DonationAmountTooLow.into()); + } + + let donation_data = &mut ctx.accounts.donation_data; + donation_data.amount_collecting = amount; + donation_data.ending_timestamp = ending_timestamp; + donation_data.is_closed = false; + donation_data.recipient = ctx.accounts.recipient.key(); + donation_data.creator_data = ctx.accounts.creator_data.key(); + donation_data.donation_protocol = ctx.accounts.donation_protocol.key(); + donation_data.holding_wallet = ctx.accounts.holding_wallet.key(); + donation_data.donation_mint = ctx.accounts.donation_mint.key(); + donation_data.holding_bump = holding_bump; + donation_data.ipfs_hash = ipfs_hash; + + let default_donation_mint = &ctx.accounts.default_donation_mint; + let donation_mint = &ctx.accounts.donation_mint; + let is_default_token_mint_0 = ctx.accounts.pool_state.load()?.token_mint_0 == ctx.accounts.donation_protocol.donation_mint; + + let amount = calculate_amount( + default_donation_mint.decimals, + donation_mint.decimals, + amount, + ctx.accounts.pool_state.load()?.sqrt_price_x64, + is_default_token_mint_0, + ); + + let creator_data = &mut ctx.accounts.creator_data; + creator_data.total_amount_collecting = creator_data.total_amount_collecting.checked_add(amount).unwrap(); + creator_data.donations_created_count = creator_data.donations_created_count.checked_add(1).unwrap(); + + Ok(()) +} diff --git a/programs/donaproto/src/instructions/mod.rs b/programs/donaproto/src/instructions/mod.rs index 18cde9a..1e174f0 100644 --- a/programs/donaproto/src/instructions/mod.rs +++ b/programs/donaproto/src/instructions/mod.rs @@ -14,3 +14,5 @@ pub mod authorize_pool; pub use authorize_pool::*; pub mod authorize_clmm; pub use authorize_clmm::*; +pub mod create_donation_v2; +pub use create_donation_v2::*; diff --git a/programs/donaproto/src/lib.rs b/programs/donaproto/src/lib.rs index 91548c1..3b24735 100644 --- a/programs/donaproto/src/lib.rs +++ b/programs/donaproto/src/lib.rs @@ -4,6 +4,7 @@ use instructions::*; pub mod errors; pub mod states; +pub mod utils; declare_id!("HbNNG85aBuR9W5F8YobTeDRRmXWFbDhLDS6WbLzWbLhH"); @@ -58,4 +59,14 @@ pub mod donaproto { pub fn authorize_clmm_pool(ctx: Context) -> Result<()> { instructions::authorize_clmm_pool(ctx) } + + pub fn create_donation_v2( + ctx: Context, + amount: u64, + ipfs_hash: String, + ending_timestamp: u64, + holding_bump: u8, + ) -> Result<()> { + instructions::create_donation_v2(ctx, amount, ipfs_hash, ending_timestamp, holding_bump) + } } diff --git a/programs/donaproto/src/states/donation_data.rs b/programs/donaproto/src/states/donation_data.rs index 81b75b1..6f585a6 100644 --- a/programs/donaproto/src/states/donation_data.rs +++ b/programs/donaproto/src/states/donation_data.rs @@ -14,6 +14,7 @@ pub struct DonationData { pub donation_protocol: Pubkey, pub holding_wallet: Pubkey, pub creator_data: Pubkey, + pub donation_mint: Pubkey, pub holding_bump: u8, pub ipfs_hash: String, } @@ -29,6 +30,7 @@ impl DonationData { const DONATION_PROTOCOL_LEN: usize = mem::size_of::(); const HOLDING_WALLET_LEN: usize = mem::size_of::(); const CREATOR_DATA_LEN: usize = mem::size_of::(); + const DONATION_MINT_LEN: usize = mem::size_of::(); const HOLDING_BUMP_LEN: usize = mem::size_of::(); const IPFS_HASH_LEN: usize = MAX_IPFS_HASH_LEN; @@ -41,6 +43,7 @@ impl DonationData { + DonationData::DONATION_PROTOCOL_LEN + DonationData::HOLDING_WALLET_LEN + DonationData::CREATOR_DATA_LEN + + DonationData::DONATION_MINT_LEN + DonationData::HOLDING_BUMP_LEN + DonationData::IPFS_HASH_LEN; } diff --git a/programs/donaproto/src/utils.rs b/programs/donaproto/src/utils.rs new file mode 100644 index 0000000..cde59c1 --- /dev/null +++ b/programs/donaproto/src/utils.rs @@ -0,0 +1,59 @@ +/// A library for handling Q64.64 fixed point numbers +/// copied from `raydium-clmm` program library +// TODO: write tests for this module + +pub const Q64: u128 = (u64::MAX as u128) + 1; // 2^64 +pub const RESOLUTION: u8 = 64; + +pub fn multipler(decimals: u8) -> f64 { + (10_i32).checked_pow(decimals.try_into().unwrap()).unwrap() as f64 +} + +pub fn from_x64_price(price: u128) -> f64 { + price as f64 / Q64 as f64 +} + +pub fn sqrt_price_x64_to_price(price: u128, decimals_0: u8, decimals_1: u8) -> f64 { + from_x64_price(price).powi(2) * multipler(decimals_0) / multipler(decimals_1) +} + +pub fn identify_mint_decimals( + default_donation_mint_decimals: u8, + donation_mint_decimals: u8, + is_default_token_mint_0: bool, +) -> (u8, u8) { + if is_default_token_mint_0 { + return (default_donation_mint_decimals, donation_mint_decimals); + } + + (donation_mint_decimals, default_donation_mint_decimals) +} + +pub fn amount_from_price( + amount: u64, + price: f64, + is_default_token_mint_0: bool, +) -> u64 { + if is_default_token_mint_0 { + return (amount as f64 / price).round() as u64; + } + + (amount as f64 * price).round() as u64 +} + +pub fn calculate_amount( + default_donation_mint_decimals: u8, + donation_mint_decimals: u8, + amount: u64, + sqrt_price_x64: u128, + is_default_token_mint_0: bool, +) -> u64 { + let (decimals_0, decimals_1) = identify_mint_decimals( + default_donation_mint_decimals, + donation_mint_decimals, + is_default_token_mint_0, + ); + let price = sqrt_price_x64_to_price(sqrt_price_x64, decimals_0, decimals_1); + + amount_from_price(amount, price, is_default_token_mint_0) +}