lib.rs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  1. //! An IDO pool program implementing the Mango Markets token sale design here:
  2. //! https://docs.mango.markets/litepaper#token-sale.
  3. use anchor_lang::prelude::*;
  4. use anchor_lang::solana_program::program_option::COption;
  5. use anchor_spl::token::{self, Burn, Mint, MintTo, TokenAccount, Transfer};
  6. #[program]
  7. pub mod ido_pool {
  8. use super::*;
  9. #[access_control(InitializePool::accounts(&ctx, nonce) future_start_time(&ctx, start_ido_ts))]
  10. pub fn initialize_pool(
  11. ctx: Context<InitializePool>,
  12. num_ido_tokens: u64,
  13. nonce: u8,
  14. start_ido_ts: i64,
  15. end_deposits_ts: i64,
  16. end_ido_ts: i64,
  17. ) -> Result<()> {
  18. if !(start_ido_ts < end_deposits_ts && end_deposits_ts < end_ido_ts) {
  19. return Err(ErrorCode::SeqTimes.into());
  20. }
  21. let pool_account = &mut ctx.accounts.pool_account;
  22. pool_account.redeemable_mint = *ctx.accounts.redeemable_mint.to_account_info().key;
  23. pool_account.pool_watermelon = *ctx.accounts.pool_watermelon.to_account_info().key;
  24. pool_account.watermelon_mint = ctx.accounts.pool_watermelon.mint;
  25. pool_account.pool_usdc = *ctx.accounts.pool_usdc.to_account_info().key;
  26. pool_account.distribution_authority = *ctx.accounts.distribution_authority.key;
  27. pool_account.nonce = nonce;
  28. pool_account.num_ido_tokens = num_ido_tokens;
  29. pool_account.start_ido_ts = start_ido_ts;
  30. pool_account.end_deposits_ts = end_deposits_ts;
  31. pool_account.end_ido_ts = end_ido_ts;
  32. // Transfer Watermelon from creator to pool account.
  33. let cpi_accounts = Transfer {
  34. from: ctx.accounts.creator_watermelon.to_account_info(),
  35. to: ctx.accounts.pool_watermelon.to_account_info(),
  36. authority: ctx.accounts.distribution_authority.to_account_info(),
  37. };
  38. let cpi_program = ctx.accounts.token_program.clone();
  39. let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
  40. token::transfer(cpi_ctx, num_ido_tokens)?;
  41. Ok(())
  42. }
  43. #[access_control(unrestricted_phase(&ctx))]
  44. pub fn exchange_usdc_for_redeemable(
  45. ctx: Context<ExchangeUsdcForRedeemable>,
  46. amount: u64,
  47. ) -> Result<()> {
  48. // While token::transfer will check this, we prefer a verbose err msg.
  49. if ctx.accounts.user_usdc.amount < amount {
  50. return Err(ErrorCode::LowUsdc.into());
  51. }
  52. // Transfer user's USDC to pool USDC account.
  53. let cpi_accounts = Transfer {
  54. from: ctx.accounts.user_usdc.to_account_info(),
  55. to: ctx.accounts.pool_usdc.to_account_info(),
  56. authority: ctx.accounts.user_authority.clone(),
  57. };
  58. let cpi_program = ctx.accounts.token_program.to_account_info();
  59. let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
  60. token::transfer(cpi_ctx, amount)?;
  61. // Mint Redeemable to user Redeemable account.
  62. let seeds = &[
  63. ctx.accounts.pool_account.watermelon_mint.as_ref(),
  64. &[ctx.accounts.pool_account.nonce],
  65. ];
  66. let signer = &[&seeds[..]];
  67. let cpi_accounts = MintTo {
  68. mint: ctx.accounts.redeemable_mint.to_account_info(),
  69. to: ctx.accounts.user_redeemable.to_account_info(),
  70. authority: ctx.accounts.pool_signer.clone(),
  71. };
  72. let cpi_program = ctx.accounts.token_program.clone();
  73. let cpi_ctx = CpiContext::new_with_signer(cpi_program, cpi_accounts, signer);
  74. token::mint_to(cpi_ctx, amount)?;
  75. Ok(())
  76. }
  77. #[access_control(withdraw_only_phase(&ctx))]
  78. pub fn exchange_redeemable_for_usdc(
  79. ctx: Context<ExchangeRedeemableForUsdc>,
  80. amount: u64,
  81. ) -> Result<()> {
  82. // While token::burn will check this, we prefer a verbose err msg.
  83. if ctx.accounts.user_redeemable.amount < amount {
  84. return Err(ErrorCode::LowRedeemable.into());
  85. }
  86. // Burn the user's redeemable tokens.
  87. let cpi_accounts = Burn {
  88. mint: ctx.accounts.redeemable_mint.to_account_info(),
  89. to: ctx.accounts.user_redeemable.to_account_info(),
  90. authority: ctx.accounts.user_authority.to_account_info(),
  91. };
  92. let cpi_program = ctx.accounts.token_program.clone();
  93. let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
  94. token::burn(cpi_ctx, amount)?;
  95. // Transfer USDC from pool account to user.
  96. let seeds = &[
  97. ctx.accounts.pool_account.watermelon_mint.as_ref(),
  98. &[ctx.accounts.pool_account.nonce],
  99. ];
  100. let signer = &[&seeds[..]];
  101. let cpi_accounts = Transfer {
  102. from: ctx.accounts.pool_usdc.to_account_info(),
  103. to: ctx.accounts.user_usdc.to_account_info(),
  104. authority: ctx.accounts.pool_signer.to_account_info(),
  105. };
  106. let cpi_program = ctx.accounts.token_program.clone();
  107. let cpi_ctx = CpiContext::new_with_signer(cpi_program, cpi_accounts, signer);
  108. token::transfer(cpi_ctx, amount)?;
  109. Ok(())
  110. }
  111. #[access_control(ido_over(&ctx.accounts.pool_account, &ctx.accounts.clock))]
  112. pub fn exchange_redeemable_for_watermelon(
  113. ctx: Context<ExchangeRedeemableForWatermelon>,
  114. amount: u64,
  115. ) -> Result<()> {
  116. // While token::burn will check this, we prefer a verbose err msg.
  117. if ctx.accounts.user_redeemable.amount < amount {
  118. return Err(ErrorCode::LowRedeemable.into());
  119. }
  120. // Calculate watermelon tokens due.
  121. let watermelon_amount = (amount as u128)
  122. .checked_mul(ctx.accounts.pool_watermelon.amount as u128)
  123. .unwrap()
  124. .checked_div(ctx.accounts.redeemable_mint.supply as u128)
  125. .unwrap();
  126. // Burn the user's redeemable tokens.
  127. let cpi_accounts = Burn {
  128. mint: ctx.accounts.redeemable_mint.to_account_info(),
  129. to: ctx.accounts.user_redeemable.to_account_info(),
  130. authority: ctx.accounts.user_authority.to_account_info(),
  131. };
  132. let cpi_program = ctx.accounts.token_program.clone();
  133. let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
  134. token::burn(cpi_ctx, amount)?;
  135. // Transfer Watermelon from pool account to user.
  136. let seeds = &[
  137. ctx.accounts.pool_account.watermelon_mint.as_ref(),
  138. &[ctx.accounts.pool_account.nonce],
  139. ];
  140. let signer = &[&seeds[..]];
  141. let cpi_accounts = Transfer {
  142. from: ctx.accounts.pool_watermelon.to_account_info(),
  143. to: ctx.accounts.user_watermelon.to_account_info(),
  144. authority: ctx.accounts.pool_signer.to_account_info(),
  145. };
  146. let cpi_program = ctx.accounts.token_program.clone();
  147. let cpi_ctx = CpiContext::new_with_signer(cpi_program, cpi_accounts, signer);
  148. token::transfer(cpi_ctx, watermelon_amount as u64)?;
  149. Ok(())
  150. }
  151. #[access_control(ido_over(&ctx.accounts.pool_account, &ctx.accounts.clock))]
  152. pub fn withdraw_pool_usdc(ctx: Context<WithdrawPoolUsdc>) -> Result<()> {
  153. // Transfer total USDC from pool account to creator account.
  154. let seeds = &[
  155. ctx.accounts.pool_account.watermelon_mint.as_ref(),
  156. &[ctx.accounts.pool_account.nonce],
  157. ];
  158. let signer = &[&seeds[..]];
  159. let cpi_accounts = Transfer {
  160. from: ctx.accounts.pool_usdc.to_account_info(),
  161. to: ctx.accounts.creator_usdc.to_account_info(),
  162. authority: ctx.accounts.pool_signer.to_account_info(),
  163. };
  164. let cpi_program = ctx.accounts.token_program.clone();
  165. let cpi_ctx = CpiContext::new_with_signer(cpi_program, cpi_accounts, signer);
  166. token::transfer(cpi_ctx, ctx.accounts.pool_usdc.amount)?;
  167. Ok(())
  168. }
  169. }
  170. #[derive(Accounts)]
  171. pub struct InitializePool<'info> {
  172. #[account(init)]
  173. pub pool_account: ProgramAccount<'info, PoolAccount>,
  174. pub pool_signer: AccountInfo<'info>,
  175. #[account(
  176. "redeemable_mint.mint_authority == COption::Some(*pool_signer.key)",
  177. "redeemable_mint.supply == 0"
  178. )]
  179. pub redeemable_mint: CpiAccount<'info, Mint>,
  180. #[account("usdc_mint.decimals == redeemable_mint.decimals")]
  181. pub usdc_mint: CpiAccount<'info, Mint>,
  182. #[account(mut, "pool_watermelon.owner == *pool_signer.key")]
  183. pub pool_watermelon: CpiAccount<'info, TokenAccount>,
  184. #[account("pool_usdc.owner == *pool_signer.key")]
  185. pub pool_usdc: CpiAccount<'info, TokenAccount>,
  186. #[account(signer)]
  187. pub distribution_authority: AccountInfo<'info>,
  188. #[account(mut, "creator_watermelon.owner == *distribution_authority.key")]
  189. pub creator_watermelon: CpiAccount<'info, TokenAccount>,
  190. #[account("token_program.key == &token::ID")]
  191. pub token_program: AccountInfo<'info>,
  192. pub rent: Sysvar<'info, Rent>,
  193. pub clock: Sysvar<'info, Clock>,
  194. }
  195. impl<'info> InitializePool<'info> {
  196. fn accounts(ctx: &Context<InitializePool<'info>>, nonce: u8) -> Result<()> {
  197. let expected_signer = Pubkey::create_program_address(
  198. &[ctx.accounts.pool_watermelon.mint.as_ref(), &[nonce]],
  199. ctx.program_id,
  200. )
  201. .map_err(|_| ErrorCode::InvalidNonce)?;
  202. if ctx.accounts.pool_signer.key != &expected_signer {
  203. return Err(ErrorCode::InvalidNonce.into());
  204. }
  205. Ok(())
  206. }
  207. }
  208. #[derive(Accounts)]
  209. pub struct ExchangeUsdcForRedeemable<'info> {
  210. #[account(has_one = redeemable_mint, has_one = pool_usdc)]
  211. pub pool_account: ProgramAccount<'info, PoolAccount>,
  212. #[account(seeds = [pool_account.watermelon_mint.as_ref(), &[pool_account.nonce]])]
  213. pool_signer: AccountInfo<'info>,
  214. #[account(
  215. mut,
  216. "redeemable_mint.mint_authority == COption::Some(*pool_signer.key)"
  217. )]
  218. pub redeemable_mint: CpiAccount<'info, Mint>,
  219. #[account(mut, "pool_usdc.owner == *pool_signer.key")]
  220. pub pool_usdc: CpiAccount<'info, TokenAccount>,
  221. #[account(signer)]
  222. pub user_authority: AccountInfo<'info>,
  223. #[account(mut, "user_usdc.owner == *user_authority.key")]
  224. pub user_usdc: CpiAccount<'info, TokenAccount>,
  225. #[account(mut, "user_redeemable.owner == *user_authority.key")]
  226. pub user_redeemable: CpiAccount<'info, TokenAccount>,
  227. #[account("token_program.key == &token::ID")]
  228. pub token_program: AccountInfo<'info>,
  229. pub clock: Sysvar<'info, Clock>,
  230. }
  231. #[derive(Accounts)]
  232. pub struct ExchangeRedeemableForUsdc<'info> {
  233. #[account(has_one = redeemable_mint, has_one = pool_usdc)]
  234. pub pool_account: ProgramAccount<'info, PoolAccount>,
  235. #[account(seeds = [pool_account.watermelon_mint.as_ref(), &[pool_account.nonce]])]
  236. pool_signer: AccountInfo<'info>,
  237. #[account(
  238. mut,
  239. "redeemable_mint.mint_authority == COption::Some(*pool_signer.key)"
  240. )]
  241. pub redeemable_mint: CpiAccount<'info, Mint>,
  242. #[account(mut, "pool_usdc.owner == *pool_signer.key")]
  243. pub pool_usdc: CpiAccount<'info, TokenAccount>,
  244. #[account(signer)]
  245. pub user_authority: AccountInfo<'info>,
  246. #[account(mut, "user_usdc.owner == *user_authority.key")]
  247. pub user_usdc: CpiAccount<'info, TokenAccount>,
  248. #[account(mut, "user_redeemable.owner == *user_authority.key")]
  249. pub user_redeemable: CpiAccount<'info, TokenAccount>,
  250. #[account("token_program.key == &token::ID")]
  251. pub token_program: AccountInfo<'info>,
  252. pub clock: Sysvar<'info, Clock>,
  253. }
  254. #[derive(Accounts)]
  255. pub struct ExchangeRedeemableForWatermelon<'info> {
  256. #[account(has_one = redeemable_mint, has_one = pool_watermelon)]
  257. pub pool_account: ProgramAccount<'info, PoolAccount>,
  258. #[account(seeds = [pool_account.watermelon_mint.as_ref(), &[pool_account.nonce]])]
  259. pool_signer: AccountInfo<'info>,
  260. #[account(
  261. mut,
  262. "redeemable_mint.mint_authority == COption::Some(*pool_signer.key)"
  263. )]
  264. pub redeemable_mint: CpiAccount<'info, Mint>,
  265. #[account(mut, "pool_watermelon.owner == *pool_signer.key")]
  266. pub pool_watermelon: CpiAccount<'info, TokenAccount>,
  267. #[account(signer)]
  268. pub user_authority: AccountInfo<'info>,
  269. #[account(mut, "user_watermelon.owner == *user_authority.key")]
  270. pub user_watermelon: CpiAccount<'info, TokenAccount>,
  271. #[account(mut, "user_redeemable.owner == *user_authority.key")]
  272. pub user_redeemable: CpiAccount<'info, TokenAccount>,
  273. #[account("token_program.key == &token::ID")]
  274. pub token_program: AccountInfo<'info>,
  275. pub clock: Sysvar<'info, Clock>,
  276. }
  277. #[derive(Accounts)]
  278. pub struct WithdrawPoolUsdc<'info> {
  279. #[account(has_one = pool_usdc, has_one = distribution_authority)]
  280. pub pool_account: ProgramAccount<'info, PoolAccount>,
  281. #[account(seeds = [pool_account.watermelon_mint.as_ref(), &[pool_account.nonce]])]
  282. pub pool_signer: AccountInfo<'info>,
  283. #[account(mut, "pool_usdc.owner == *pool_signer.key")]
  284. pub pool_usdc: CpiAccount<'info, TokenAccount>,
  285. #[account(signer)]
  286. pub distribution_authority: AccountInfo<'info>,
  287. #[account(mut, "creator_usdc.owner == *distribution_authority.key")]
  288. pub creator_usdc: CpiAccount<'info, TokenAccount>,
  289. #[account("token_program.key == &token::ID")]
  290. pub token_program: AccountInfo<'info>,
  291. pub clock: Sysvar<'info, Clock>,
  292. }
  293. #[account]
  294. pub struct PoolAccount {
  295. pub redeemable_mint: Pubkey,
  296. pub pool_watermelon: Pubkey,
  297. pub watermelon_mint: Pubkey,
  298. pub pool_usdc: Pubkey,
  299. pub distribution_authority: Pubkey,
  300. pub nonce: u8,
  301. pub num_ido_tokens: u64,
  302. pub start_ido_ts: i64,
  303. pub end_deposits_ts: i64,
  304. pub end_ido_ts: i64,
  305. }
  306. #[error]
  307. pub enum ErrorCode {
  308. #[msg("IDO must start in the future")]
  309. IdoFuture,
  310. #[msg("IDO times are non-sequential")]
  311. SeqTimes,
  312. #[msg("IDO has not started")]
  313. StartIdoTime,
  314. #[msg("Deposits period has ended")]
  315. EndDepositsTime,
  316. #[msg("IDO has ended")]
  317. EndIdoTime,
  318. #[msg("IDO has not finished yet")]
  319. IdoNotOver,
  320. #[msg("Insufficient USDC")]
  321. LowUsdc,
  322. #[msg("Insufficient redeemable tokens")]
  323. LowRedeemable,
  324. #[msg("USDC total and redeemable total don't match")]
  325. UsdcNotEqRedeem,
  326. #[msg("Given nonce is invalid")]
  327. InvalidNonce,
  328. }
  329. // Access control modifiers.
  330. // Asserts the IDO starts in the future.
  331. fn future_start_time<'info>(ctx: &Context<InitializePool<'info>>, start_ido_ts: i64) -> Result<()> {
  332. if !(ctx.accounts.clock.unix_timestamp < start_ido_ts) {
  333. return Err(ErrorCode::IdoFuture.into());
  334. }
  335. Ok(())
  336. }
  337. // Asserts the IDO is in the first phase.
  338. fn unrestricted_phase<'info>(ctx: &Context<ExchangeUsdcForRedeemable<'info>>) -> Result<()> {
  339. if !(ctx.accounts.pool_account.start_ido_ts < ctx.accounts.clock.unix_timestamp) {
  340. return Err(ErrorCode::StartIdoTime.into());
  341. } else if !(ctx.accounts.clock.unix_timestamp < ctx.accounts.pool_account.end_deposits_ts) {
  342. return Err(ErrorCode::EndDepositsTime.into());
  343. }
  344. Ok(())
  345. }
  346. // Asserts the IDO is in the second phase.
  347. fn withdraw_only_phase(ctx: &Context<ExchangeRedeemableForUsdc>) -> Result<()> {
  348. if !(ctx.accounts.pool_account.start_ido_ts < ctx.accounts.clock.unix_timestamp) {
  349. return Err(ErrorCode::StartIdoTime.into());
  350. } else if !(ctx.accounts.clock.unix_timestamp < ctx.accounts.pool_account.end_ido_ts) {
  351. return Err(ErrorCode::EndIdoTime.into());
  352. }
  353. Ok(())
  354. }
  355. // Asserts the IDO sale period has ended, based on the current timestamp.
  356. fn ido_over<'info>(
  357. pool_account: &ProgramAccount<'info, PoolAccount>,
  358. clock: &Sysvar<'info, Clock>,
  359. ) -> Result<()> {
  360. if !(pool_account.end_ido_ts < clock.unix_timestamp) {
  361. return Err(ErrorCode::IdoNotOver.into());
  362. }
  363. Ok(())
  364. }