diff --git a/Anchor.toml b/Anchor.toml index 84235fb..aa55c49 100644 --- a/Anchor.toml +++ b/Anchor.toml @@ -12,7 +12,7 @@ stockpile_v2 = "STKUaKniasuqrfer3XNbmrrc578pkL1XACdK8H3YPu8" url = "https://api.apr.dev" [provider] -cluster = "mainnet" +cluster = "devnet" wallet = "/home/sav/.config/solana/id.json" [scripts] diff --git a/Cargo.lock b/Cargo.lock index 2092457..c20cb0c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -77,7 +77,7 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faa5be5b72abea167f87c868379ba3c2be356bfca9e6f474fd055fa0f7eeb4f2" dependencies = [ - "anchor-syn", + "anchor-syn 0.28.0", "anyhow", "proc-macro2", "quote", @@ -91,7 +91,7 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f468970344c7c9f9d03b4da854fd7c54f21305059f53789d0045c1dd803f0018" dependencies = [ - "anchor-syn", + "anchor-syn 0.28.0", "anyhow", "bs58 0.5.0", "proc-macro2", @@ -106,7 +106,7 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59948e7f9ef8144c2aefb3f32a40c5fce2798baeec765ba038389e82301017ef" dependencies = [ - "anchor-syn", + "anchor-syn 0.28.0", "proc-macro2", "syn 1.0.109", ] @@ -117,7 +117,7 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc753c9d1c7981cb8948cf7e162fb0f64558999c0413058e2d43df1df5448086" dependencies = [ - "anchor-syn", + "anchor-syn 0.28.0", "proc-macro2", "quote", "syn 1.0.109", @@ -129,7 +129,7 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f38b4e172ba1b52078f53fdc9f11e3dc0668ad27997838a0aad2d148afac8c97" dependencies = [ - "anchor-syn", + "anchor-syn 0.28.0", "anyhow", "proc-macro2", "quote", @@ -142,7 +142,7 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4eebd21543606ab61e2d83d9da37d24d3886a49f390f9c43a1964735e8c0f0d5" dependencies = [ - "anchor-syn", + "anchor-syn 0.28.0", "anyhow", "proc-macro2", "quote", @@ -155,7 +155,7 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec4720d899b3686396cced9508f23dab420f1308344456ec78ef76f98fda42af" dependencies = [ - "anchor-syn", + "anchor-syn 0.28.0", "anyhow", "proc-macro2", "quote", @@ -173,6 +173,52 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "anchor-gen" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a3b9def91d1f0c23b99be210afea0990f931a1edae439ea30e0da50baeaea8f" +dependencies = [ + "anchor-generate-cpi-crate", + "anchor-generate-cpi-interface", +] + +[[package]] +name = "anchor-generate-cpi-crate" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03e8733d55b8492bde0d6f219c25769786758ff9e5e90a16e6a348f91284af50" +dependencies = [ + "anchor-idl", + "syn 1.0.109", +] + +[[package]] +name = "anchor-generate-cpi-interface" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7092a50cf959ebf53460162cb07e88d4cc8ea7fa8c45292e80503a3186033daf" +dependencies = [ + "anchor-idl", + "darling 0.14.4", + "syn 1.0.109", +] + +[[package]] +name = "anchor-idl" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3de2665dc06ee0bd3c7d08f47f136a3d5b1e34b7ac1bc632c86a812add04d68b" +dependencies = [ + "anchor-syn 0.24.2", + "darling 0.14.4", + "heck 0.4.1", + "proc-macro2", + "quote", + "serde_json", + "syn 1.0.109", +] + [[package]] name = "anchor-lang" version = "0.28.0" @@ -210,6 +256,25 @@ dependencies = [ "spl-token-2022", ] +[[package]] +name = "anchor-syn" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03549dc2eae0b20beba6333b14520e511822a6321cdb1760f841064a69347316" +dependencies = [ + "anyhow", + "bs58 0.3.1", + "heck 0.3.3", + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "serde", + "serde_json", + "sha2 0.9.9", + "syn 1.0.109", + "thiserror", +] + [[package]] name = "anchor-syn" version = "0.28.0" @@ -218,7 +283,7 @@ checksum = "a125e4b0cc046cfec58f5aa25038e34cf440151d58f0db3afc55308251fe936d" dependencies = [ "anyhow", "bs58 0.5.0", - "heck", + "heck 0.3.3", "proc-macro2", "quote", "serde", @@ -569,6 +634,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "bs58" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "476e9cd489f9e121e02ffa6014a8ef220ecb15c05ed23fc34cca13925dc283fb" + [[package]] name = "bs58" version = "0.4.0" @@ -787,14 +858,38 @@ dependencies = [ "zeroize", ] +[[package]] +name = "darling" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" +dependencies = [ + "darling_core 0.14.4", + "darling_macro 0.14.4", +] + [[package]] name = "darling" version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.20.3", + "darling_macro 0.20.3", +] + +[[package]] +name = "darling_core" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 1.0.109", ] [[package]] @@ -811,13 +906,24 @@ dependencies = [ "syn 2.0.39", ] +[[package]] +name = "darling_macro" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" +dependencies = [ + "darling_core 0.14.4", + "quote", + "syn 1.0.109", +] + [[package]] name = "darling_macro" version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ - "darling_core", + "darling_core 0.20.3", "quote", "syn 2.0.39", ] @@ -1001,6 +1107,12 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -1138,6 +1250,16 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "klend" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbcd2e9d9ca85813cc23a7a29c2417351a426636e48e7e4082d2e8724292de56" +dependencies = [ + "anchor-gen", + "anchor-lang", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -1465,6 +1587,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proc-macro2-diagnostics" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bf29726d67464d49fa6224a1d07936a8c08bb3fba727c7493f6cf1616fdaada" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", + "yansi", +] + [[package]] name = "pyth-sdk" version = "0.8.0" @@ -1771,7 +1906,7 @@ version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "881b6f881b17d13214e5d494c939ebab463d01264ce1811e9d4ac3a882e7695f" dependencies = [ - "darling", + "darling 0.20.3", "proc-macro2", "quote", "syn 2.0.39", @@ -2128,6 +2263,16 @@ dependencies = [ "thiserror", ] +[[package]] +name = "stockpile-trusts" +version = "0.1.0" +dependencies = [ + "anchor-lang", + "anchor-spl", + "klend", + "stockpile-v2", +] + [[package]] name = "stockpile-v2" version = "0.1.0" @@ -2491,6 +2636,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "yansi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" + [[package]] name = "zerocopy" version = "0.7.25" diff --git a/programs/stockpile-trusts/Cargo.toml b/programs/stockpile-trusts/Cargo.toml new file mode 100644 index 0000000..6b3e29d --- /dev/null +++ b/programs/stockpile-trusts/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "stockpile-trusts" +version = "0.1.0" +description = "Created with Anchor" +edition = "2021" + +[lib] +crate-type = ["cdylib", "lib"] +name = "stockpile_trusts" + +[features] +no-entrypoint = [] +no-idl = [] +no-log-ix-name = [] +cpi = ["no-entrypoint"] +default = [] + +[dependencies] +anchor-lang = { version = "0.28.0", features = ["init-if-needed"] } +anchor-spl = "0.28.0" +klend = "0.1.0" +stockpile-v2 = { path = "../stockpile-v2", features = ["cpi"]} \ No newline at end of file diff --git a/programs/stockpile-trusts/src/error.rs b/programs/stockpile-trusts/src/error.rs new file mode 100644 index 0000000..438820e --- /dev/null +++ b/programs/stockpile-trusts/src/error.rs @@ -0,0 +1,15 @@ +use anchor_lang::prelude::*; + +#[error_code] +pub enum VaultError { + + #[msg("Attempting to withdraw from vault with account that is not the authority")] + WrongVaultAuthority, + + #[msg("The project account provided is invalid")] + ProjectAccountInvalid, + + #[msg("This mint is not current supported")] + MintNotSupported, + +} \ No newline at end of file diff --git a/programs/stockpile-v2/src/instructions/contribute_milestone.rs b/programs/stockpile-trusts/src/events.rs similarity index 100% rename from programs/stockpile-v2/src/instructions/contribute_milestone.rs rename to programs/stockpile-trusts/src/events.rs diff --git a/programs/stockpile-trusts/src/instructions/deposit.rs b/programs/stockpile-trusts/src/instructions/deposit.rs new file mode 100644 index 0000000..428d675 --- /dev/null +++ b/programs/stockpile-trusts/src/instructions/deposit.rs @@ -0,0 +1,54 @@ +use anchor_lang::prelude::*; +use anchor_spl::{token, associated_token}; +use klend::*; + +use crate::{error::VaultError, util::KLend}; + +pub fn deposit( + ctx: Context, + _project_id: u64, + amount: u64 +) -> Result<()> { + // Check to make sure the token is supported + let cpi_ctx: CpiContext<'_, '_, '_, '_, klend::cpi::accounts::DepositReserveLiquidity<'_>> = CpiContext::new( + ctx.accounts.kamino_program.to_account_info(), + cpi::accounts::DepositReserveLiquidity { + owner: ctx.accounts.payer.to_account_info(), + reserve: ctx.accounts.reserve.to_account_info(), + reserve_collateral_mint: ctx.accounts.mint.to_account_info(), + lending_market: ctx.accounts.lending_market.to_account_info(), + lending_market_authority: ctx.accounts.lending_market_authority.to_account_info(), + reserve_liquidity_supply: ctx.accounts.reserve_liquidity_supply.to_account_info(), + user_source_liquidity: ctx.accounts.payer_token_account.to_account_info(), + user_destination_collateral: ctx.accounts.payer_token_account.to_account_info(), + token_program: ctx.accounts.token_program.to_account_info() + }, + ); + + cpi::deposit_reserve_liquidity(cpi_ctx, amount) + .map_err(|_e| { + msg!("Kamino deposit failed."); + VaultError::WrongVaultAuthority + })?; + + Ok(()) +} + +#[derive(Accounts)] +#[instruction( + project_id: u64, +)] +pub struct Deposit<'info> { + pub lending_market: AccountInfo<'info>, + pub lending_market_authority: AccountInfo<'info>, + pub mint: Account<'info, token::Mint>, + pub reserve_liquidity_supply: AccountInfo<'info>, + pub reserve: AccountInfo<'info>, + #[account(mut)] + pub payer: Signer<'info>, + pub payer_token_account: Account<'info, token::TokenAccount>, + pub kamino_program: Program<'info, KLend>, + pub system_program: Program<'info, System>, + pub token_program: Program<'info, token::Token>, + pub associated_token_program: Program<'info, associated_token::AssociatedToken>, +} \ No newline at end of file diff --git a/programs/stockpile-trusts/src/instructions/init.rs b/programs/stockpile-trusts/src/instructions/init.rs new file mode 100644 index 0000000..29ce391 --- /dev/null +++ b/programs/stockpile-trusts/src/instructions/init.rs @@ -0,0 +1,50 @@ +use anchor_lang::prelude::*; + +use crate::{Protocols, Intervals, YieldVault}; + +pub fn init( + ctx: Context, + _vault_id: u64, + protocol: Protocols, + interval: Intervals, + initial_amount: u64, + projects: Vec, + mint: Pubkey, +) -> Result<()> { + ctx.accounts.vault.set_inner( + YieldVault::new( + protocol, + interval, + initial_amount, + _vault_id, + projects, + mint, + *ctx.bumps + .get("project") + .expect("Failed to derive bump for `project`"), + )? + ); + Ok(()) +} + +#[derive(Accounts)] +#[instruction( + vault_id: u64, +)] +pub struct InitializeVault<'info> { + #[account( + init, + payer = payer, + space = YieldVault::SPACE, + seeds = [ + YieldVault::SEED_PREFIX.as_bytes(), + vault_id.to_le_bytes().as_ref(), + payer.key().as_ref() + ], + bump, + )] + pub vault: Account<'info, YieldVault>, + #[account(mut)] + pub payer: Signer<'info>, + pub system_program: Program<'info, System>, +} \ No newline at end of file diff --git a/programs/stockpile-trusts/src/instructions/mod.rs b/programs/stockpile-trusts/src/instructions/mod.rs new file mode 100644 index 0000000..59bc591 --- /dev/null +++ b/programs/stockpile-trusts/src/instructions/mod.rs @@ -0,0 +1,7 @@ +pub mod deposit; +pub mod withdraw; +pub mod init; + +pub use deposit::*; +pub use withdraw::*; +pub use init::*; \ No newline at end of file diff --git a/programs/stockpile-trusts/src/instructions/withdraw.rs b/programs/stockpile-trusts/src/instructions/withdraw.rs new file mode 100644 index 0000000..0c98244 --- /dev/null +++ b/programs/stockpile-trusts/src/instructions/withdraw.rs @@ -0,0 +1,99 @@ +use anchor_lang::prelude::*; +use anchor_spl::{token, associated_token}; +use stockpile_v2::{cpi::*, state::project::Project}; +use klend::*; + +use crate::{error::VaultError, util::{KLend, Stockpile}, YieldVault}; + +pub fn withdraw( + ctx: Context, + _project_id: u64, + amount: u64 +) -> Result<()> { + let project_accounts = ctx.remaining_accounts; + // Create Kamino CPI context + let cpi_ctx: CpiContext<'_, '_, '_, '_, klend::cpi::accounts::RedeemReserveCollateral<'_>> = CpiContext::new( + ctx.accounts.kamino_program.to_account_info(), + cpi::accounts::RedeemReserveCollateral { + owner: ctx.accounts.payer.to_account_info(), + reserve: ctx.accounts.reserve.to_account_info(), + reserve_collateral_mint: ctx.accounts.mint.to_account_info(), + lending_market: ctx.accounts.lending_market.to_account_info(), + lending_market_authority: ctx.accounts.lending_market_authority.to_account_info(), + reserve_liquidity_supply: ctx.accounts.reserve_liquidity_supply.to_account_info(), + user_source_collateral: ctx.accounts.payer_token_account.to_account_info(), + user_destination_liquidity: ctx.accounts.payer_token_account.to_account_info(), + token_program: ctx.accounts.token_program.to_account_info() + }, + ); + + // Redeem Kamino funds + cpi::redeem_reserve_collateral(cpi_ctx, amount) + .map_err(|_e| { + msg!("Kamino redemption failed."); + VaultError::WrongVaultAuthority + })?; + + // TO-DO: Convert the below into a loop that runs through + // the "project_accounts", validates that they are indeed + // project accounts, creates CPI context, invokes, and maybe + // enforces a max account limit so it doesn't overrun the 1232 limit. + + // Create Stockpile CPI context + let contribute_ctx: CpiContext<'_, '_, '_, '_, stockpile_v2::cpi::accounts::Contribute<'_>> = CpiContext::new( + ctx.accounts.stockpile_program.to_account_info(), + stockpile_v2::cpi::accounts::Contribute { + project: ctx.accounts.project.to_account_info(), + payer: ctx.accounts.payer.to_account_info(), + mint: ctx.accounts.mint.to_account_info(), + project_token_account: ctx.accounts.project_token_account.to_account_info(), + payer_token_account: ctx.accounts.payer_token_account.to_account_info(), + system_program: ctx.accounts.system_program.to_account_info(), + token_program: ctx.accounts.token_program.to_account_info(), + associated_token_program: ctx.accounts.associated_token_program.to_account_info() + } + ); + + // Contribute to Stockpile project + contribute(contribute_ctx, _project_id, amount) + .map_err(|_e| { + msg!("Kamino redemption failed."); + VaultError::ProjectAccountInvalid + })?; + + Ok(()) +} + +#[derive(Accounts)] +#[instruction( + project_id: u64, +)] +pub struct Withdraw<'info> { + #[account(mut)] + pub vault: Account<'info, YieldVault>, + #[account(mut)] + pub project: Account<'info, Project>, + #[account(mut)] + pub project_token_account: Account<'info, token::TokenAccount>, + #[account(mut)] + pub lending_market: AccountInfo<'info>, + #[account(mut)] + pub lending_market_authority: AccountInfo<'info>, + pub mint: Account<'info, token::Mint>, + #[account(mut)] + pub reserve_liquidity_supply: AccountInfo<'info>, + #[account(mut)] + pub reserve: AccountInfo<'info>, + pub payer: Signer<'info>, + #[account( + mut, + constraint = payer_token_account.owner == payer.key() + )] + pub payer_token_account: Account<'info, token::TokenAccount>, + pub stockpile_program: Program<'info, Stockpile>, + pub kamino_program: Program<'info, KLend>, + pub clock: Sysvar<'info, Clock>, + pub system_program: Program<'info, System>, + pub token_program: Program<'info, token::Token>, + pub associated_token_program: Program<'info, associated_token::AssociatedToken>, +} \ No newline at end of file diff --git a/programs/stockpile-trusts/src/lib.rs b/programs/stockpile-trusts/src/lib.rs new file mode 100644 index 0000000..b64b2dd --- /dev/null +++ b/programs/stockpile-trusts/src/lib.rs @@ -0,0 +1,31 @@ +use anchor_lang::prelude::*; + +pub mod instructions; +pub mod util; +pub mod state; +pub mod error; + +pub use instructions::*; +use crate::state::vault::*; + +declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"); + +#[program] +pub mod stockpile_trusts { + use super::*; + + pub fn initialize_vault(ctx: Context, vault_id: u64, protocol: Protocols, interval: Intervals, initial_amount: u64, projects: Vec, mint: Pubkey) -> Result<()> { + instructions::init(ctx, vault_id, protocol, interval, initial_amount, projects, mint) + } + + pub fn deposit(ctx: Context, project_id: u64, amount: u64) -> Result<()> { + instructions::deposit(ctx, project_id, amount) + } + + pub fn withdraw_and_close(ctx: Context, project_id: u64, amount: u64) -> Result<()> { + instructions::withdraw(ctx, project_id, amount) + } +} + +#[derive(Accounts)] +pub struct Initialize {} diff --git a/programs/stockpile-trusts/src/state/mod.rs b/programs/stockpile-trusts/src/state/mod.rs new file mode 100644 index 0000000..a6c9a65 --- /dev/null +++ b/programs/stockpile-trusts/src/state/mod.rs @@ -0,0 +1,3 @@ +pub mod vault; + +pub use vault::*; \ No newline at end of file diff --git a/programs/stockpile-trusts/src/state/vault.rs b/programs/stockpile-trusts/src/state/vault.rs new file mode 100644 index 0000000..742234e --- /dev/null +++ b/programs/stockpile-trusts/src/state/vault.rs @@ -0,0 +1,64 @@ +use anchor_lang::prelude::*; + +#[account] +pub struct YieldVault { + pub protocol: Protocols, + pub interval: Intervals, + pub initial_amount: u64, + pub vault_id: u64, + pub projects: Vec, + pub mint: Pubkey, + pub bump: u8, +} + +impl YieldVault { + pub const SEED_PREFIX: &'static str = "yield_vault"; + + pub const SPACE: usize = 8 + + 4 // u64 + + 4 // String + + 4 // u64 + + 4 // u64 + + 4 // u64 + + 1 // u8 + + 160 // Vec (Max 5) + + 32 // Pubkey + + 1 // u8 + + 4 // Enum (Singleton) + + 250; // Padding + + pub fn new(protocol: Protocols, interval: Intervals, initial_amount: u64, vault_id: u64, projects: Vec, mint: Pubkey, bump: u8) -> Result { + Ok(Self { + protocol, + interval, + initial_amount, + vault_id, + projects, + mint, + bump + }) + } +} + +#[derive(AnchorSerialize, AnchorDeserialize, Clone, PartialEq, Eq)] +pub enum Protocols { + Kamino, +} + +impl Default for Protocols { + fn default() -> Self { + Protocols::Kamino + } +} + +#[derive(AnchorSerialize, AnchorDeserialize, Clone, PartialEq, Eq)] +pub enum Intervals { + Weekly, + Monthly +} + +impl Default for Intervals { + fn default() -> Self { + Intervals::Weekly + } +} \ No newline at end of file diff --git a/programs/stockpile-trusts/src/util.rs b/programs/stockpile-trusts/src/util.rs new file mode 100644 index 0000000..542c000 --- /dev/null +++ b/programs/stockpile-trusts/src/util.rs @@ -0,0 +1,88 @@ +use anchor_lang::prelude::*; +use anchor_lang::system_program; +use std::str::FromStr; + +use crate::error::VaultError; + +pub const MAX_NAME_LEN: usize = 100; +pub const MAX_ADMIN_LEN: usize = 4; + +pub const STOCKPILE_PROGRAM_ID: &str = "STKUaKniasuqrfer3XNbmrrc578pkL1XACdK8H3YPu8"; +pub const KAMINO_PROGRAM_ID: &str = "KLend2g3cP87fffoy8q1mQqGKjrxjC8boSyAYavgmjD"; + +pub const USDC_MINT: &str = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"; +pub const USDC_DEVNET_MINT: &str = "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU"; + +pub const SUPPORTED_SPL_MINTS: [&'static str; 2] = [USDC_MINT, USDC_DEVNET_MINT]; + +pub fn to_pubkey(string: &str) -> Pubkey { + Pubkey::from_str(&string).expect("Error parsing public key from string.") +} + +pub fn mint_is_supported(mint_pubkey: &Pubkey) -> Result<()> { + for suppported_mint in SUPPORTED_SPL_MINTS { + if to_pubkey(suppported_mint).eq(mint_pubkey) { + return Ok(()); + } + } + Err(VaultError::MintNotSupported.into()) +} + +pub fn set_and_maybe_realloc<'info, T>( + account: &mut Account<'info, T>, + new_data: &T, + payer: AccountInfo<'info>, + system_program: AccountInfo<'info>, +) -> Result<()> +where + T: AccountDeserialize + + AccountSerialize + + borsh::BorshDeserialize + + borsh::BorshSerialize + + Clone + anchor_lang::Owner, +{ + let account_info = account.to_account_info(); + + // See if it needs to be reallocated + let new_account_size = (new_data.try_to_vec()?).len(); + if new_account_size > account_info.data_len() { + // Determine additional rent required + let lamports_required = (Rent::get()?).minimum_balance(new_account_size); + let additional_rent_to_fund = lamports_required - account_info.lamports(); + + // Perform transfer of additional rent + system_program::transfer( + CpiContext::new( + system_program, + system_program::Transfer { + from: payer, + to: account_info.clone(), + }, + ), + additional_rent_to_fund, + )?; + + // Serialize new data + account_info.realloc(new_account_size, false)?; + } + account.set_inner(new_data.clone()); + Ok(()) +} + +#[derive(Clone)] +pub struct KLend; + +impl anchor_lang::Id for KLend { + fn id() -> Pubkey { + Pubkey::from_str(KAMINO_PROGRAM_ID).unwrap() + } +} + +#[derive(Clone)] +pub struct Stockpile; + +impl anchor_lang::Id for Stockpile { + fn id() -> Pubkey { + Pubkey::from_str(STOCKPILE_PROGRAM_ID).unwrap() + } +} \ No newline at end of file diff --git a/programs/stockpile-v2/src/Xargo.toml b/programs/stockpile-v2/src/Xargo.toml new file mode 100644 index 0000000..475fb71 --- /dev/null +++ b/programs/stockpile-v2/src/Xargo.toml @@ -0,0 +1,2 @@ +[target.bpfel-unknown-unknown.dependencies.std] +features = [] diff --git a/programs/stockpile-v2/src/instructions/contribute_milestone_with_vote.rs b/programs/stockpile-v2/src/instructions/contribute_milestone_with_vote.rs deleted file mode 100644 index e69de29..0000000 diff --git a/programs/stockpile-v2/src/instructions/contribute_with_vote.rs b/programs/stockpile-v2/src/instructions/contribute_with_vote.rs index a371d8a..f2d94ce 100644 --- a/programs/stockpile-v2/src/instructions/contribute_with_vote.rs +++ b/programs/stockpile-v2/src/instructions/contribute_with_vote.rs @@ -18,8 +18,10 @@ use crate::util::{ // Makes a contribution to a fundraiser that is // currently participating in a pool. Requires // that the fundraiser has invoked the "join_pool" -// instruction, and is actively participating. Also -// requires the payer have a valid Civic pass. +// instruction, and is actively participating. +// ------- REQUIRES ONE OF TWO CRITERIA --------- +// 1. Caller holds a valid Civic Pass +// 2. Caller is verified via the relayer pub fn contribute_with_vote( ctx: Context, _pool_id: u64, @@ -27,8 +29,8 @@ pub fn contribute_with_vote( amount: u64, ) -> Result<()> { // Define verification options - let gatekeeper_network = ctx.accounts.gatekeeper_network.key(); let payer = ctx.accounts.payer.key(); + let project = &mut ctx.accounts.project; // Check to make sure the token is supported mint_is_supported(&ctx.accounts.mint.key())?; @@ -36,25 +38,29 @@ pub fn contribute_with_vote( // Check to make sure the pool is not closed ctx.accounts.pool.is_active()?; - // Perform Civic pass verification - Gateway::verify_gateway_token_account_info( - &ctx.accounts.gateway_token_account.to_account_info(), - &ctx.accounts.payer.key(), - &gatekeeper_network, - None, - ).map_err(|_e| { - msg!("Gateway token verification failed."); - ProtocolError::CivicFailure - }); + // Perform Civic pass verification if no optional account + if ctx.accounts.relayer.is_none() { + let gatekeeper_network = ctx.accounts.gatekeeper_network.clone().unwrap(); + + Gateway::verify_gateway_token_account_info( + &ctx.accounts.gateway_token_account.clone().unwrap(), + &ctx.accounts.payer.key(), + &gatekeeper_network.key(), + None, + ).map_err(|_e| { + msg!("Gateway token verification failed."); + ProtocolError::CivicFailure + })?; + } // Add the project to the shares, if it doesn't exist - let project_key = ctx.accounts.project.key(); + let project_key = &project.key(); let mut pool_data = ctx.accounts.pool.clone().into_inner(); // Iterate through the Participants, and // check if the project exists in the pool // If not: break function and return error - if let Some(participant) = pool_data.project_shares.iter_mut().find(|p| p.project_key == project_key) { + if let Some(participant) = pool_data.project_shares.iter_mut().find(|p| p.project_key == *project_key) { let vote_ticket = VoteTicket::new( payer, Some(ctx.accounts.mint.key()), @@ -91,16 +97,16 @@ pub fn contribute_with_vote( amount, )?; - // Increment fields - ctx.accounts.project.raised += amount; - ctx.accounts.project.balance += amount; - ctx.accounts.project.contributors += 1; - // Update the QF algorithm ctx.accounts.pool.update_shares( ctx.accounts.pyth_usdc_usd.to_account_info(), )?; + // Increment fields + project.raised += amount; + project.balance += amount; + project.contributors += 1; + Ok(()) } @@ -127,6 +133,7 @@ pub struct ContributeWithVote<'info> { )] pub pool: Box>, #[account( + mut, seeds = [ Project::SEED_PREFIX.as_bytes(), project_id.to_le_bytes().as_ref(), @@ -148,11 +155,12 @@ pub struct ContributeWithVote<'info> { )] pub payer_token_account: Account<'info, token::TokenAccount>, /// CHECK: This is not unsafe because this account isn't written to - pub gateway_token_account: AccountInfo<'info>, + pub gateway_token_account: Option>, /// CHECK: This is not unsafe because this account isn't written to - pub gatekeeper_network: AccountInfo<'info>, + pub gatekeeper_network: Option>, #[account(mut)] pub payer: Signer<'info>, + pub relayer: Option>, pub system_program: Program<'info, System>, pub token_program: Program<'info, token::Token>, pub associated_token_program: Program<'info, associated_token::AssociatedToken>, diff --git a/programs/stockpile-v2/src/instructions/extend_pool_start.rs b/programs/stockpile-v2/src/instructions/extend_pool_start.rs new file mode 100644 index 0000000..306fa13 --- /dev/null +++ b/programs/stockpile-v2/src/instructions/extend_pool_start.rs @@ -0,0 +1,41 @@ +use anchor_lang::prelude::*; + +use crate::{state::pool::*, error::ProtocolError}; + +/// Extends the duration of the pool by +/// updating it's end field with a new timestamp. +/// This instruction can only extend a pool, not shorten. +pub fn extend_pool_start( + ctx: Context, + _pool_id: u64, + new_start_date: u64, +) -> Result<()> { + let pool = &mut ctx.accounts.pool; + let payer = &ctx.accounts.payer; + + require!(pool.admins.contains(&payer.key()), ProtocolError::NotAuthorized); + + pool.start = new_start_date; + + Ok(()) +} + +#[derive(Accounts)] +#[instruction( + pool_id: u64, + _new_start_date: u64, +)] +pub struct ExtendPoolStart<'info> { + #[account( + mut, + seeds = [ + Pool::SEED_PREFIX.as_bytes(), + pool_id.to_le_bytes().as_ref(), + ], + bump = pool.bump, + )] + pub pool: Account<'info, Pool>, + #[account(mut)] + pub payer: Signer<'info>, + pub system_program: Program<'info, System>, +} \ No newline at end of file diff --git a/programs/stockpile-v2/src/instructions/mod.rs b/programs/stockpile-v2/src/instructions/mod.rs index 90ab83a..a470ecc 100644 --- a/programs/stockpile-v2/src/instructions/mod.rs +++ b/programs/stockpile-v2/src/instructions/mod.rs @@ -2,8 +2,6 @@ pub mod add_project; pub mod close_milestone; pub mod close_project; pub mod contribute; -pub mod contribute_milestone; -pub mod contribute_milestone_with_vote; pub mod contribute_with_vote; pub mod create_milestone; pub mod create_pool; @@ -18,14 +16,14 @@ pub mod claim_payout; pub mod withdraw_funds_from_round; pub mod update_project; pub mod extend_pool_duration; +pub mod extend_pool_start; pub mod update_pool; +pub mod realloc_pool; pub use add_project::*; pub use close_milestone::*; pub use close_project::*; pub use contribute::*; -pub use contribute_milestone::*; // Instruction not currently exposed -pub use contribute_milestone_with_vote::*; // Instruction not currently exposed pub use contribute_with_vote::*; pub use create_milestone::*; pub use create_pool::*; @@ -41,3 +39,5 @@ pub use withdraw_funds_from_round::*; pub use update_project::*; pub use update_pool::*; pub use extend_pool_duration::*; +pub use extend_pool_start::*; +pub use realloc_pool::*; diff --git a/programs/stockpile-v2/src/instructions/realloc_pool.rs b/programs/stockpile-v2/src/instructions/realloc_pool.rs new file mode 100644 index 0000000..c508935 --- /dev/null +++ b/programs/stockpile-v2/src/instructions/realloc_pool.rs @@ -0,0 +1,35 @@ +use anchor_lang::prelude::*; +use crate::state::pool::*; + +pub fn realloc_pool( + ctx: Context, + _pool_id: u64, + +) -> Result<()> { + ctx.accounts.pool.to_account_info().realloc((ctx.accounts.pool.try_to_vec()?).len() + 9000, false)?; + + Ok(()) +} + +#[derive(Accounts)] +#[instruction( + pool_id: u64, +)] +pub struct ReallocPool<'info> { + #[account( + mut, + realloc = Pool::SPACE + 9000, + realloc::payer = payer, + realloc::zero = false, + seeds = [ + Pool::SEED_PREFIX.as_bytes(), + pool_id.to_le_bytes().as_ref(), + ], + bump = pool.bump, + )] + pub pool: Box>, + pub gatekeeper_network: Option>, + #[account(mut)] + pub payer: Signer<'info>, + pub system_program: Program<'info, System>, +} \ No newline at end of file diff --git a/programs/stockpile-v2/src/instructions/withdraw.rs b/programs/stockpile-v2/src/instructions/withdraw.rs index 9313e2d..89072c3 100644 --- a/programs/stockpile-v2/src/instructions/withdraw.rs +++ b/programs/stockpile-v2/src/instructions/withdraw.rs @@ -34,7 +34,7 @@ pub fn withdraw(ctx: Context, amount: u64) -> Result<()> { amount, )?; - project.balance -= amount; + //project.balance -= amount; Ok(()) } diff --git a/programs/stockpile-v2/src/lib.rs b/programs/stockpile-v2/src/lib.rs index 6ac8ccb..ddce920 100644 --- a/programs/stockpile-v2/src/lib.rs +++ b/programs/stockpile-v2/src/lib.rs @@ -46,7 +46,7 @@ use solana_security_txt::security_txt; security_txt! { name: "Stockpile V2", project_url: "http://stockpile.so", - contacts: "email:info@stockpile.so,discord:0xsavant", + contacts: "email:joey@stockpile.so,discord:0xsavant", policy: "https://github.com/StockpileLabs/stockpile-v2/blob/master/SECURITY.md", preferred_languages: "en", source_code: "https://github.com/StockpileLabs/stockpile-v2" @@ -209,4 +209,19 @@ pub mod stockpile_v2 { ) -> Result<()> { instructions::extend_pool_duration(ctx, _pool_id, new_end_date) } + + pub fn extend_pool_start( + ctx: Context, + _pool_id: u64, + new_start_date: u64, + ) -> Result<()> { + instructions::extend_pool_start(ctx, _pool_id, new_start_date) + } + + pub fn realloc_pool( + ctx: Context, + _pool_id: u64, + ) -> Result<()> { + instructions::realloc_pool(ctx, _pool_id) + } } diff --git a/programs/stockpile-v2/src/state/pool.rs b/programs/stockpile-v2/src/state/pool.rs index 9c0aa97..3abf154 100644 --- a/programs/stockpile-v2/src/state/pool.rs +++ b/programs/stockpile-v2/src/state/pool.rs @@ -1,6 +1,6 @@ use anchor_lang::prelude::*; use pyth_sdk_solana::load_price_feed_from_account_info; -use std::collections::BTreeMap; +use std::collections::{BTreeMap, HashMap}; use crate::{ error::ProtocolError, @@ -173,14 +173,12 @@ impl Pool { // Get the current prices for each mint in USD let usdc_usd_price = try_load_price(pyth_usdc_usd)?; - msg!("USDC/USD Price: {:?}", usdc_usd_price); - let (vote_count, sum_of_squared_votes_all_projects) = { // Block-scope the mutability - // Set up a `BTreeMap` to use to record each project's squared sum of + // Set up a `HashMap` to use to record each project's squared sum of // square roots of votes - let mut vote_count_mut: BTreeMap = BTreeMap::new(); + let mut vote_count_mut: HashMap = HashMap::new(); let mut sum_of_squared_votes_all_projects_mut: f64 = 0.0; // Iterate through all of the projects @@ -196,7 +194,7 @@ impl Pool { // Square the sum of all square roots of each vote let sum_of_roots_squared = total_square_root_votes_usd.powi(2); - msg!("Sum of square roots squared: {:?}", sum_of_roots_squared); + msg!("Sum of roots squared: {:?}", sum_of_roots_squared); // Add to the vote count `BTreeMap` vote_count_mut.insert(project.project_key, sum_of_roots_squared); diff --git a/programs/stockpile-v2/src/util.rs b/programs/stockpile-v2/src/util.rs index 3539747..2b2313d 100644 --- a/programs/stockpile-v2/src/util.rs +++ b/programs/stockpile-v2/src/util.rs @@ -13,6 +13,8 @@ pub const USDC_DEVNET_MINT: &str = "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU pub const SOL_USD_PRICE_FEED_ID: &str = "ALP8SdU9oARYVLgLR7LrqMNCYBnhtnQz1cj6bwgwQmgj"; // THIS IS MAINNET pub const USDC_USD_PRICE_FEED_ID: &str = "Gnt27xtC473ZT2Mw5u8wZ68Z3gULkSTb5DuxJy7eJotD"; +//THIS IS DEVNET +//pub const USDC_USD_PRICE_FEED_ID: &str = "5SSkXsEKQepHHAewytPVwdej4epN1nxgLVM84L4KXgy7"; pub const SUPPORTED_SPL_MINTS: [&'static str; 2] = [USDC_MINT, USDC_DEVNET_MINT];