lib.rs 46 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331
  1. //! A relatively advanced example of a staking program. If you're new to Anchor,
  2. //! it's suggested to start with the other examples.
  3. use anchor_lang::prelude::*;
  4. use anchor_lang::solana_program::program_option::COption;
  5. use anchor_spl::token::{self, Mint, TokenAccount, Transfer};
  6. use lockup::{CreateVesting, RealizeLock, Realizor, Vesting};
  7. use std::convert::Into;
  8. #[program]
  9. mod registry {
  10. use super::*;
  11. #[state]
  12. pub struct Registry {
  13. pub lockup_program: Pubkey,
  14. }
  15. impl Registry {
  16. pub fn new(ctx: Context<Ctor>) -> Result<Self> {
  17. Ok(Registry {
  18. lockup_program: *ctx.accounts.lockup_program.key,
  19. })
  20. }
  21. pub fn set_lockup_program(
  22. &mut self,
  23. ctx: Context<SetLockupProgram>,
  24. lockup_program: Pubkey,
  25. ) -> Result<()> {
  26. // Hard code the authority because the first version of this program
  27. // did not set an authority account in the global state.
  28. //
  29. // When removing the program's upgrade authority, one should remove
  30. // this method first, redeploy, then remove the upgrade authority.
  31. let expected: Pubkey = "HUgFuN4PbvF5YzjDSw9dQ8uTJUcwm2ANsMXwvRdY4ABx"
  32. .parse()
  33. .unwrap();
  34. if ctx.accounts.authority.key != &expected {
  35. return Err(ErrorCode::InvalidProgramAuthority.into());
  36. }
  37. self.lockup_program = lockup_program;
  38. Ok(())
  39. }
  40. }
  41. impl<'info> RealizeLock<'info, IsRealized<'info>> for Registry {
  42. fn is_realized(ctx: Context<IsRealized>, v: Vesting) -> ProgramResult {
  43. if let Some(realizor) = &v.realizor {
  44. if &realizor.metadata != ctx.accounts.member.to_account_info().key {
  45. return Err(ErrorCode::InvalidRealizorMetadata.into());
  46. }
  47. assert!(ctx.accounts.member.beneficiary == v.beneficiary);
  48. let total_staked =
  49. ctx.accounts.member_spt.amount + ctx.accounts.member_spt_locked.amount;
  50. if total_staked != 0 {
  51. return Err(ErrorCode::UnrealizedReward.into());
  52. }
  53. }
  54. Ok(())
  55. }
  56. }
  57. #[access_control(Initialize::accounts(&ctx, nonce))]
  58. pub fn initialize(
  59. ctx: Context<Initialize>,
  60. mint: Pubkey,
  61. authority: Pubkey,
  62. nonce: u8,
  63. withdrawal_timelock: i64,
  64. stake_rate: u64,
  65. reward_q_len: u32,
  66. ) -> Result<()> {
  67. let registrar = &mut ctx.accounts.registrar;
  68. registrar.authority = authority;
  69. registrar.nonce = nonce;
  70. registrar.mint = mint;
  71. registrar.pool_mint = *ctx.accounts.pool_mint.to_account_info().key;
  72. registrar.stake_rate = stake_rate;
  73. registrar.reward_event_q = *ctx.accounts.reward_event_q.to_account_info().key;
  74. registrar.withdrawal_timelock = withdrawal_timelock;
  75. let reward_q = &mut ctx.accounts.reward_event_q;
  76. reward_q
  77. .events
  78. .resize(reward_q_len as usize, Default::default());
  79. Ok(())
  80. }
  81. pub fn update_registrar(
  82. ctx: Context<UpdateRegistrar>,
  83. new_authority: Option<Pubkey>,
  84. withdrawal_timelock: Option<i64>,
  85. ) -> Result<()> {
  86. let registrar = &mut ctx.accounts.registrar;
  87. if let Some(new_authority) = new_authority {
  88. registrar.authority = new_authority;
  89. }
  90. if let Some(withdrawal_timelock) = withdrawal_timelock {
  91. registrar.withdrawal_timelock = withdrawal_timelock;
  92. }
  93. Ok(())
  94. }
  95. #[access_control(CreateMember::accounts(&ctx, nonce))]
  96. pub fn create_member(ctx: Context<CreateMember>, nonce: u8) -> Result<()> {
  97. let member = &mut ctx.accounts.member;
  98. member.registrar = *ctx.accounts.registrar.to_account_info().key;
  99. member.beneficiary = *ctx.accounts.beneficiary.key;
  100. member.balances = (&ctx.accounts.balances).into();
  101. member.balances_locked = (&ctx.accounts.balances_locked).into();
  102. member.nonce = nonce;
  103. Ok(())
  104. }
  105. pub fn update_member(ctx: Context<UpdateMember>, metadata: Option<Pubkey>) -> Result<()> {
  106. let member = &mut ctx.accounts.member;
  107. if let Some(m) = metadata {
  108. member.metadata = m;
  109. }
  110. Ok(())
  111. }
  112. // Deposits that can only come directly from the member beneficiary.
  113. pub fn deposit(ctx: Context<Deposit>, amount: u64) -> Result<()> {
  114. token::transfer(ctx.accounts.into(), amount).map_err(Into::into)
  115. }
  116. // Deposits that can only come from the beneficiary's vesting accounts.
  117. pub fn deposit_locked(ctx: Context<DepositLocked>, amount: u64) -> Result<()> {
  118. token::transfer(ctx.accounts.into(), amount).map_err(Into::into)
  119. }
  120. #[access_control(no_available_rewards(
  121. &ctx.accounts.reward_event_q,
  122. &ctx.accounts.member,
  123. &ctx.accounts.balances,
  124. &ctx.accounts.balances_locked,
  125. ))]
  126. pub fn stake(ctx: Context<Stake>, spt_amount: u64, locked: bool) -> Result<()> {
  127. let balances = {
  128. if locked {
  129. &ctx.accounts.balances_locked
  130. } else {
  131. &ctx.accounts.balances
  132. }
  133. };
  134. // Transfer tokens into the stake vault.
  135. {
  136. let seeds = &[
  137. ctx.accounts.registrar.to_account_info().key.as_ref(),
  138. ctx.accounts.member.to_account_info().key.as_ref(),
  139. &[ctx.accounts.member.nonce],
  140. ];
  141. let member_signer = &[&seeds[..]];
  142. let cpi_ctx = CpiContext::new_with_signer(
  143. ctx.accounts.token_program.clone(),
  144. token::Transfer {
  145. from: balances.vault.to_account_info(),
  146. to: balances.vault_stake.to_account_info(),
  147. authority: ctx.accounts.member_signer.to_account_info(),
  148. },
  149. member_signer,
  150. );
  151. // Convert from stake-token units to mint-token units.
  152. let token_amount = spt_amount
  153. .checked_mul(ctx.accounts.registrar.stake_rate)
  154. .unwrap();
  155. token::transfer(cpi_ctx, token_amount)?;
  156. }
  157. // Mint pool tokens to the staker.
  158. {
  159. let seeds = &[
  160. ctx.accounts.registrar.to_account_info().key.as_ref(),
  161. &[ctx.accounts.registrar.nonce],
  162. ];
  163. let registrar_signer = &[&seeds[..]];
  164. let cpi_ctx = CpiContext::new_with_signer(
  165. ctx.accounts.token_program.clone(),
  166. token::MintTo {
  167. mint: ctx.accounts.pool_mint.to_account_info(),
  168. to: balances.spt.to_account_info(),
  169. authority: ctx.accounts.registrar_signer.to_account_info(),
  170. },
  171. registrar_signer,
  172. );
  173. token::mint_to(cpi_ctx, spt_amount)?;
  174. }
  175. // Update stake timestamp.
  176. let member = &mut ctx.accounts.member;
  177. member.last_stake_ts = ctx.accounts.clock.unix_timestamp;
  178. Ok(())
  179. }
  180. #[access_control(no_available_rewards(
  181. &ctx.accounts.reward_event_q,
  182. &ctx.accounts.member,
  183. &ctx.accounts.balances,
  184. &ctx.accounts.balances_locked,
  185. ))]
  186. pub fn start_unstake(ctx: Context<StartUnstake>, spt_amount: u64, locked: bool) -> Result<()> {
  187. let balances = {
  188. if locked {
  189. &ctx.accounts.balances_locked
  190. } else {
  191. &ctx.accounts.balances
  192. }
  193. };
  194. // Program signer.
  195. let seeds = &[
  196. ctx.accounts.registrar.to_account_info().key.as_ref(),
  197. ctx.accounts.member.to_account_info().key.as_ref(),
  198. &[ctx.accounts.member.nonce],
  199. ];
  200. let member_signer = &[&seeds[..]];
  201. // Burn pool tokens.
  202. {
  203. let cpi_ctx = CpiContext::new_with_signer(
  204. ctx.accounts.token_program.clone(),
  205. token::Burn {
  206. mint: ctx.accounts.pool_mint.to_account_info(),
  207. to: balances.spt.to_account_info(),
  208. authority: ctx.accounts.member_signer.to_account_info(),
  209. },
  210. member_signer,
  211. );
  212. token::burn(cpi_ctx, spt_amount)?;
  213. }
  214. // Convert from stake-token units to mint-token units.
  215. let token_amount = spt_amount
  216. .checked_mul(ctx.accounts.registrar.stake_rate)
  217. .unwrap();
  218. // Transfer tokens from the stake to pending vault.
  219. {
  220. let cpi_ctx = CpiContext::new_with_signer(
  221. ctx.accounts.token_program.clone(),
  222. token::Transfer {
  223. from: balances.vault_stake.to_account_info(),
  224. to: balances.vault_pw.to_account_info(),
  225. authority: ctx.accounts.member_signer.to_account_info(),
  226. },
  227. member_signer,
  228. );
  229. token::transfer(cpi_ctx, token_amount)?;
  230. }
  231. // Print receipt.
  232. let pending_withdrawal = &mut ctx.accounts.pending_withdrawal;
  233. pending_withdrawal.burned = false;
  234. pending_withdrawal.member = *ctx.accounts.member.to_account_info().key;
  235. pending_withdrawal.start_ts = ctx.accounts.clock.unix_timestamp;
  236. pending_withdrawal.end_ts =
  237. ctx.accounts.clock.unix_timestamp + ctx.accounts.registrar.withdrawal_timelock;
  238. pending_withdrawal.amount = token_amount;
  239. pending_withdrawal.pool = ctx.accounts.registrar.pool_mint;
  240. pending_withdrawal.registrar = *ctx.accounts.registrar.to_account_info().key;
  241. pending_withdrawal.locked = locked;
  242. // Update stake timestamp.
  243. let member = &mut ctx.accounts.member;
  244. member.last_stake_ts = ctx.accounts.clock.unix_timestamp;
  245. Ok(())
  246. }
  247. pub fn end_unstake(ctx: Context<EndUnstake>) -> Result<()> {
  248. if ctx.accounts.pending_withdrawal.end_ts > ctx.accounts.clock.unix_timestamp {
  249. return Err(ErrorCode::UnstakeTimelock.into());
  250. }
  251. // Select which balance set this affects.
  252. let balances = {
  253. if ctx.accounts.pending_withdrawal.locked {
  254. &ctx.accounts.member.balances_locked
  255. } else {
  256. &ctx.accounts.member.balances
  257. }
  258. };
  259. // Check the vaults given are corrrect.
  260. if &balances.vault != ctx.accounts.vault.key {
  261. return Err(ErrorCode::InvalidVault.into());
  262. }
  263. if &balances.vault_pw != ctx.accounts.vault_pw.key {
  264. return Err(ErrorCode::InvalidVault.into());
  265. }
  266. // Transfer tokens between vaults.
  267. {
  268. let seeds = &[
  269. ctx.accounts.registrar.to_account_info().key.as_ref(),
  270. ctx.accounts.member.to_account_info().key.as_ref(),
  271. &[ctx.accounts.member.nonce],
  272. ];
  273. let signer = &[&seeds[..]];
  274. let cpi_ctx = CpiContext::new_with_signer(
  275. ctx.accounts.token_program.clone(),
  276. Transfer {
  277. from: ctx.accounts.vault_pw.to_account_info(),
  278. to: ctx.accounts.vault.to_account_info(),
  279. authority: ctx.accounts.member_signer.clone(),
  280. },
  281. signer,
  282. );
  283. token::transfer(cpi_ctx, ctx.accounts.pending_withdrawal.amount)?;
  284. }
  285. // Burn the pending withdrawal receipt.
  286. let pending_withdrawal = &mut ctx.accounts.pending_withdrawal;
  287. pending_withdrawal.burned = true;
  288. Ok(())
  289. }
  290. pub fn withdraw(ctx: Context<Withdraw>, amount: u64) -> Result<()> {
  291. let seeds = &[
  292. ctx.accounts.registrar.to_account_info().key.as_ref(),
  293. ctx.accounts.member.to_account_info().key.as_ref(),
  294. &[ctx.accounts.member.nonce],
  295. ];
  296. let signer = &[&seeds[..]];
  297. let cpi_accounts = Transfer {
  298. from: ctx.accounts.vault.to_account_info(),
  299. to: ctx.accounts.depositor.to_account_info(),
  300. authority: ctx.accounts.member_signer.clone(),
  301. };
  302. let cpi_program = ctx.accounts.token_program.clone();
  303. let cpi_ctx = CpiContext::new_with_signer(cpi_program, cpi_accounts, signer);
  304. token::transfer(cpi_ctx, amount).map_err(Into::into)
  305. }
  306. pub fn withdraw_locked(ctx: Context<WithdrawLocked>, amount: u64) -> Result<()> {
  307. let seeds = &[
  308. ctx.accounts.registrar.to_account_info().key.as_ref(),
  309. ctx.accounts.member.to_account_info().key.as_ref(),
  310. &[ctx.accounts.member.nonce],
  311. ];
  312. let signer = &[&seeds[..]];
  313. let cpi_accounts = Transfer {
  314. from: ctx.accounts.member_vault.to_account_info(),
  315. to: ctx.accounts.vesting_vault.to_account_info(),
  316. authority: ctx.accounts.member_signer.clone(),
  317. };
  318. let cpi_program = ctx.accounts.token_program.clone();
  319. let cpi_ctx = CpiContext::new_with_signer(cpi_program, cpi_accounts, signer);
  320. token::transfer(cpi_ctx, amount).map_err(Into::into)
  321. }
  322. #[access_control(DropReward::accounts(&ctx, nonce))]
  323. pub fn drop_reward(
  324. ctx: Context<DropReward>,
  325. kind: RewardVendorKind,
  326. total: u64,
  327. expiry_ts: i64,
  328. expiry_receiver: Pubkey,
  329. nonce: u8,
  330. ) -> Result<()> {
  331. if total < ctx.accounts.pool_mint.supply {
  332. return Err(ErrorCode::InsufficientReward.into());
  333. }
  334. if ctx.accounts.clock.unix_timestamp >= expiry_ts {
  335. return Err(ErrorCode::InvalidExpiry.into());
  336. }
  337. if let RewardVendorKind::Locked {
  338. start_ts,
  339. end_ts,
  340. period_count,
  341. } = kind
  342. {
  343. if !lockup::is_valid_schedule(start_ts, end_ts, period_count) {
  344. return Err(ErrorCode::InvalidVestingSchedule.into());
  345. }
  346. }
  347. // Transfer funds into the vendor's vault.
  348. token::transfer(ctx.accounts.into(), total)?;
  349. // Add the event to the reward queue.
  350. let reward_q = &mut ctx.accounts.reward_event_q;
  351. let cursor = reward_q.append(RewardEvent {
  352. vendor: *ctx.accounts.vendor.to_account_info().key,
  353. ts: ctx.accounts.clock.unix_timestamp,
  354. locked: kind != RewardVendorKind::Unlocked,
  355. })?;
  356. // Initialize the vendor.
  357. let vendor = &mut ctx.accounts.vendor;
  358. vendor.registrar = *ctx.accounts.registrar.to_account_info().key;
  359. vendor.vault = *ctx.accounts.vendor_vault.to_account_info().key;
  360. vendor.mint = ctx.accounts.vendor_vault.mint;
  361. vendor.nonce = nonce;
  362. vendor.pool_token_supply = ctx.accounts.pool_mint.supply;
  363. vendor.reward_event_q_cursor = cursor;
  364. vendor.start_ts = ctx.accounts.clock.unix_timestamp;
  365. vendor.expiry_ts = expiry_ts;
  366. vendor.expiry_receiver = expiry_receiver;
  367. vendor.from = *ctx.accounts.depositor_authority.key;
  368. vendor.total = total;
  369. vendor.expired = false;
  370. vendor.kind = kind;
  371. Ok(())
  372. }
  373. #[access_control(reward_eligible(&ctx.accounts.cmn))]
  374. pub fn claim_reward(ctx: Context<ClaimReward>) -> Result<()> {
  375. if RewardVendorKind::Unlocked != ctx.accounts.cmn.vendor.kind {
  376. return Err(ErrorCode::ExpectedUnlockedVendor.into());
  377. }
  378. // Reward distribution.
  379. let spt_total =
  380. ctx.accounts.cmn.balances.spt.amount + ctx.accounts.cmn.balances_locked.spt.amount;
  381. let reward_amount = spt_total
  382. .checked_mul(ctx.accounts.cmn.vendor.total)
  383. .unwrap()
  384. .checked_div(ctx.accounts.cmn.vendor.pool_token_supply)
  385. .unwrap();
  386. assert!(reward_amount > 0);
  387. // Send reward to the given token account.
  388. let seeds = &[
  389. ctx.accounts.cmn.registrar.to_account_info().key.as_ref(),
  390. ctx.accounts.cmn.vendor.to_account_info().key.as_ref(),
  391. &[ctx.accounts.cmn.vendor.nonce],
  392. ];
  393. let signer = &[&seeds[..]];
  394. let cpi_ctx = CpiContext::new_with_signer(
  395. ctx.accounts.cmn.token_program.clone(),
  396. token::Transfer {
  397. from: ctx.accounts.cmn.vault.to_account_info(),
  398. to: ctx.accounts.to.to_account_info(),
  399. authority: ctx.accounts.cmn.vendor_signer.to_account_info(),
  400. },
  401. signer,
  402. );
  403. token::transfer(cpi_ctx, reward_amount)?;
  404. // Update member as having processed the reward.
  405. let member = &mut ctx.accounts.cmn.member;
  406. member.rewards_cursor = ctx.accounts.cmn.vendor.reward_event_q_cursor + 1;
  407. Ok(())
  408. }
  409. #[access_control(reward_eligible(&ctx.accounts.cmn))]
  410. pub fn claim_reward_locked<'a, 'b, 'c, 'info>(
  411. ctx: Context<'a, 'b, 'c, 'info, ClaimRewardLocked<'info>>,
  412. nonce: u8,
  413. ) -> Result<()> {
  414. let (start_ts, end_ts, period_count) = match ctx.accounts.cmn.vendor.kind {
  415. RewardVendorKind::Unlocked => return Err(ErrorCode::ExpectedLockedVendor.into()),
  416. RewardVendorKind::Locked {
  417. start_ts,
  418. end_ts,
  419. period_count,
  420. } => (start_ts, end_ts, period_count),
  421. };
  422. // Reward distribution.
  423. let spt_total =
  424. ctx.accounts.cmn.balances.spt.amount + ctx.accounts.cmn.balances_locked.spt.amount;
  425. let reward_amount = spt_total
  426. .checked_mul(ctx.accounts.cmn.vendor.total)
  427. .unwrap()
  428. .checked_div(ctx.accounts.cmn.vendor.pool_token_supply)
  429. .unwrap();
  430. assert!(reward_amount > 0);
  431. // Specify the vesting account's realizor, so that unlocks can only
  432. // execute once completely unstaked.
  433. let realizor = Some(Realizor {
  434. program: *ctx.program_id,
  435. metadata: *ctx.accounts.cmn.member.to_account_info().key,
  436. });
  437. // CPI: Create lockup account for the member's beneficiary.
  438. let seeds = &[
  439. ctx.accounts.cmn.registrar.to_account_info().key.as_ref(),
  440. ctx.accounts.cmn.vendor.to_account_info().key.as_ref(),
  441. &[ctx.accounts.cmn.vendor.nonce],
  442. ];
  443. let signer = &[&seeds[..]];
  444. let mut remaining_accounts: &[AccountInfo] = ctx.remaining_accounts;
  445. let cpi_program = ctx.accounts.lockup_program.clone();
  446. let cpi_accounts = CreateVesting::try_accounts(
  447. ctx.accounts.lockup_program.key,
  448. &mut remaining_accounts,
  449. &[],
  450. )?;
  451. let cpi_ctx = CpiContext::new_with_signer(cpi_program, cpi_accounts, signer);
  452. lockup::cpi::create_vesting(
  453. cpi_ctx,
  454. ctx.accounts.cmn.member.beneficiary,
  455. reward_amount,
  456. nonce,
  457. start_ts,
  458. end_ts,
  459. period_count,
  460. realizor,
  461. )?;
  462. // Make sure this reward can't be processed more than once.
  463. let member = &mut ctx.accounts.cmn.member;
  464. member.rewards_cursor = ctx.accounts.cmn.vendor.reward_event_q_cursor + 1;
  465. Ok(())
  466. }
  467. pub fn expire_reward(ctx: Context<ExpireReward>) -> Result<()> {
  468. if ctx.accounts.clock.unix_timestamp < ctx.accounts.vendor.expiry_ts {
  469. return Err(ErrorCode::VendorNotYetExpired.into());
  470. }
  471. // Send all remaining funds to the expiry receiver's token.
  472. let seeds = &[
  473. ctx.accounts.registrar.to_account_info().key.as_ref(),
  474. ctx.accounts.vendor.to_account_info().key.as_ref(),
  475. &[ctx.accounts.vendor.nonce],
  476. ];
  477. let signer = &[&seeds[..]];
  478. let cpi_ctx = CpiContext::new_with_signer(
  479. ctx.accounts.token_program.clone(),
  480. token::Transfer {
  481. to: ctx.accounts.expiry_receiver_token.to_account_info(),
  482. from: ctx.accounts.vault.to_account_info(),
  483. authority: ctx.accounts.vendor_signer.to_account_info(),
  484. },
  485. signer,
  486. );
  487. token::transfer(cpi_ctx, ctx.accounts.vault.amount)?;
  488. // Burn the vendor.
  489. let vendor = &mut ctx.accounts.vendor;
  490. vendor.expired = true;
  491. Ok(())
  492. }
  493. }
  494. #[derive(Accounts)]
  495. pub struct Initialize<'info> {
  496. #[account(zero)]
  497. registrar: ProgramAccount<'info, Registrar>,
  498. #[account(zero)]
  499. reward_event_q: ProgramAccount<'info, RewardQueue>,
  500. #[account("pool_mint.decimals == 0")]
  501. pool_mint: CpiAccount<'info, Mint>,
  502. }
  503. impl<'info> Initialize<'info> {
  504. fn accounts(ctx: &Context<Initialize<'info>>, nonce: u8) -> Result<()> {
  505. let registrar_signer = Pubkey::create_program_address(
  506. &[
  507. ctx.accounts.registrar.to_account_info().key.as_ref(),
  508. &[nonce],
  509. ],
  510. ctx.program_id,
  511. )
  512. .map_err(|_| ErrorCode::InvalidNonce)?;
  513. if ctx.accounts.pool_mint.mint_authority != COption::Some(registrar_signer) {
  514. return Err(ErrorCode::InvalidPoolMintAuthority.into());
  515. }
  516. assert!(ctx.accounts.pool_mint.supply == 0);
  517. Ok(())
  518. }
  519. }
  520. #[derive(Accounts)]
  521. pub struct UpdateRegistrar<'info> {
  522. #[account(mut, has_one = authority)]
  523. registrar: ProgramAccount<'info, Registrar>,
  524. #[account(signer)]
  525. authority: AccountInfo<'info>,
  526. }
  527. #[derive(Accounts)]
  528. pub struct CreateMember<'info> {
  529. // Stake instance.
  530. registrar: ProgramAccount<'info, Registrar>,
  531. // Member.
  532. #[account(zero)]
  533. member: ProgramAccount<'info, Member>,
  534. #[account(signer)]
  535. beneficiary: AccountInfo<'info>,
  536. #[account(
  537. "&balances.spt.owner == member_signer.key",
  538. "balances.spt.mint == registrar.pool_mint",
  539. "balances.vault.mint == registrar.mint"
  540. )]
  541. balances: BalanceSandboxAccounts<'info>,
  542. #[account(
  543. "&balances_locked.spt.owner == member_signer.key",
  544. "balances_locked.spt.mint == registrar.pool_mint",
  545. "balances_locked.vault.mint == registrar.mint"
  546. )]
  547. balances_locked: BalanceSandboxAccounts<'info>,
  548. member_signer: AccountInfo<'info>,
  549. // Misc.
  550. #[account("token_program.key == &token::ID")]
  551. token_program: AccountInfo<'info>,
  552. }
  553. impl<'info> CreateMember<'info> {
  554. fn accounts(ctx: &Context<CreateMember>, nonce: u8) -> Result<()> {
  555. let seeds = &[
  556. ctx.accounts.registrar.to_account_info().key.as_ref(),
  557. ctx.accounts.member.to_account_info().key.as_ref(),
  558. &[nonce],
  559. ];
  560. let member_signer = Pubkey::create_program_address(seeds, ctx.program_id)
  561. .map_err(|_| ErrorCode::InvalidNonce)?;
  562. if &member_signer != ctx.accounts.member_signer.to_account_info().key {
  563. return Err(ErrorCode::InvalidMemberSigner.into());
  564. }
  565. Ok(())
  566. }
  567. }
  568. // When creating a member, the mints and owners of these accounts are correct.
  569. // Upon creation, we assign the accounts. A onetime operation.
  570. // When using a member, we check these accounts addresess are equal to the
  571. // addresses stored on the member. If so, the correct accounts were given are
  572. // correct.
  573. #[derive(Accounts, Clone)]
  574. pub struct BalanceSandboxAccounts<'info> {
  575. #[account(mut)]
  576. spt: CpiAccount<'info, TokenAccount>,
  577. #[account(mut, constraint = vault.owner == spt.owner)]
  578. vault: CpiAccount<'info, TokenAccount>,
  579. #[account(
  580. mut,
  581. constraint = vault_stake.owner == spt.owner,
  582. constraint = vault_stake.mint == vault.mint
  583. )]
  584. vault_stake: CpiAccount<'info, TokenAccount>,
  585. #[account(mut, constraint = vault_pw.owner == spt.owner, constraint = vault_pw.mint == vault.mint)]
  586. vault_pw: CpiAccount<'info, TokenAccount>,
  587. }
  588. #[derive(Accounts)]
  589. pub struct Ctor<'info> {
  590. lockup_program: AccountInfo<'info>,
  591. }
  592. #[derive(Accounts)]
  593. pub struct SetLockupProgram<'info> {
  594. #[account(signer)]
  595. authority: AccountInfo<'info>,
  596. }
  597. #[derive(Accounts)]
  598. pub struct IsRealized<'info> {
  599. #[account(
  600. constraint = &member.balances.spt == member_spt.to_account_info().key,
  601. constraint = &member.balances_locked.spt == member_spt_locked.to_account_info().key
  602. )]
  603. member: ProgramAccount<'info, Member>,
  604. member_spt: CpiAccount<'info, TokenAccount>,
  605. member_spt_locked: CpiAccount<'info, TokenAccount>,
  606. }
  607. #[derive(Accounts)]
  608. pub struct UpdateMember<'info> {
  609. #[account(mut, has_one = beneficiary)]
  610. member: ProgramAccount<'info, Member>,
  611. #[account(signer)]
  612. beneficiary: AccountInfo<'info>,
  613. }
  614. #[derive(Accounts)]
  615. pub struct Deposit<'info> {
  616. // Member.
  617. #[account(has_one = beneficiary)]
  618. member: ProgramAccount<'info, Member>,
  619. #[account(signer)]
  620. beneficiary: AccountInfo<'info>,
  621. #[account(mut, constraint = vault.to_account_info().key == &member.balances.vault)]
  622. vault: CpiAccount<'info, TokenAccount>,
  623. // Depositor.
  624. #[account(mut)]
  625. depositor: AccountInfo<'info>,
  626. #[account(signer, constraint = depositor_authority.key == &member.beneficiary)]
  627. depositor_authority: AccountInfo<'info>,
  628. // Misc.
  629. #[account(constraint = token_program.key == &token::ID)]
  630. token_program: AccountInfo<'info>,
  631. }
  632. #[derive(Accounts)]
  633. pub struct DepositLocked<'info> {
  634. // Lockup whitelist relay interface.
  635. #[account(
  636. constraint = vesting.to_account_info().owner == &registry.lockup_program,
  637. constraint = vesting.beneficiary == member.beneficiary
  638. )]
  639. vesting: CpiAccount<'info, Vesting>,
  640. #[account(mut, constraint = vesting_vault.key == &vesting.vault)]
  641. vesting_vault: AccountInfo<'info>,
  642. // Note: no need to verify the depositor_authority since the SPL program
  643. // will fail the transaction if it's not correct.
  644. #[account(signer)]
  645. depositor_authority: AccountInfo<'info>,
  646. #[account(constraint = token_program.key == &token::ID)]
  647. token_program: AccountInfo<'info>,
  648. #[account(
  649. mut,
  650. constraint = member_vault.to_account_info().key == &member.balances_locked.vault
  651. )]
  652. member_vault: CpiAccount<'info, TokenAccount>,
  653. #[account(
  654. seeds = [registrar.to_account_info().key.as_ref(), member.to_account_info().key.as_ref()],
  655. bump = member.nonce,
  656. )]
  657. member_signer: AccountInfo<'info>,
  658. // Program specific.
  659. registry: ProgramState<'info, Registry>,
  660. registrar: ProgramAccount<'info, Registrar>,
  661. #[account(has_one = registrar, has_one = beneficiary)]
  662. member: ProgramAccount<'info, Member>,
  663. #[account(signer)]
  664. beneficiary: AccountInfo<'info>,
  665. }
  666. #[derive(Accounts)]
  667. pub struct Stake<'info> {
  668. // Global accounts for the staking instance.
  669. #[account(has_one = pool_mint, has_one = reward_event_q)]
  670. registrar: ProgramAccount<'info, Registrar>,
  671. reward_event_q: ProgramAccount<'info, RewardQueue>,
  672. #[account(mut)]
  673. pool_mint: CpiAccount<'info, Mint>,
  674. // Member.
  675. #[account(mut, has_one = beneficiary, has_one = registrar)]
  676. member: ProgramAccount<'info, Member>,
  677. #[account(signer)]
  678. beneficiary: AccountInfo<'info>,
  679. #[account(constraint = BalanceSandbox::from(&balances) == member.balances)]
  680. balances: BalanceSandboxAccounts<'info>,
  681. #[account(constraint = BalanceSandbox::from(&balances_locked) == member.balances_locked)]
  682. balances_locked: BalanceSandboxAccounts<'info>,
  683. // Program signers.
  684. #[account(
  685. seeds = [registrar.to_account_info().key.as_ref(), member.to_account_info().key.as_ref()],
  686. bump = member.nonce,
  687. )]
  688. member_signer: AccountInfo<'info>,
  689. #[account(
  690. seeds = [registrar.to_account_info().key.as_ref()],
  691. bump = registrar.nonce,
  692. )]
  693. registrar_signer: AccountInfo<'info>,
  694. // Misc.
  695. clock: Sysvar<'info, Clock>,
  696. #[account(constraint = token_program.key == &token::ID)]
  697. token_program: AccountInfo<'info>,
  698. }
  699. #[derive(Accounts)]
  700. pub struct StartUnstake<'info> {
  701. // Stake instance globals.
  702. #[account(has_one = reward_event_q)]
  703. registrar: ProgramAccount<'info, Registrar>,
  704. reward_event_q: ProgramAccount<'info, RewardQueue>,
  705. #[account(mut)]
  706. pool_mint: AccountInfo<'info>,
  707. // Member.
  708. #[account(zero)]
  709. pending_withdrawal: ProgramAccount<'info, PendingWithdrawal>,
  710. #[account(has_one = beneficiary, has_one = registrar)]
  711. member: ProgramAccount<'info, Member>,
  712. #[account(signer)]
  713. beneficiary: AccountInfo<'info>,
  714. #[account(constraint = BalanceSandbox::from(&balances) == member.balances)]
  715. balances: BalanceSandboxAccounts<'info>,
  716. #[account(constraint = BalanceSandbox::from(&balances_locked) == member.balances_locked)]
  717. balances_locked: BalanceSandboxAccounts<'info>,
  718. // Programmatic signers.
  719. #[account(
  720. seeds = [registrar.to_account_info().key.as_ref(), member.to_account_info().key.as_ref()],
  721. bump = member.nonce,
  722. )]
  723. member_signer: AccountInfo<'info>,
  724. // Misc.
  725. #[account(constraint = token_program.key == &token::ID)]
  726. token_program: AccountInfo<'info>,
  727. clock: Sysvar<'info, Clock>,
  728. }
  729. #[derive(Accounts)]
  730. pub struct EndUnstake<'info> {
  731. registrar: ProgramAccount<'info, Registrar>,
  732. #[account(has_one = registrar, has_one = beneficiary)]
  733. member: ProgramAccount<'info, Member>,
  734. #[account(signer)]
  735. beneficiary: AccountInfo<'info>,
  736. #[account(mut, has_one = registrar, has_one = member, constraint = !pending_withdrawal.burned)]
  737. pending_withdrawal: ProgramAccount<'info, PendingWithdrawal>,
  738. // If we had ordered maps implementing Accounts we could do a constraint like
  739. // balances.get(pending_withdrawal.balance_id).vault == vault.key.
  740. //
  741. // Note: we do the constraints check in the handler, not here.
  742. #[account(mut)]
  743. vault: AccountInfo<'info>,
  744. #[account(mut)]
  745. vault_pw: AccountInfo<'info>,
  746. #[account(
  747. seeds = [registrar.to_account_info().key.as_ref(), member.to_account_info().key.as_ref()],
  748. bump = member.nonce,
  749. )]
  750. member_signer: AccountInfo<'info>,
  751. clock: Sysvar<'info, Clock>,
  752. #[account(constraint = token_program.key == &token::ID)]
  753. token_program: AccountInfo<'info>,
  754. }
  755. #[derive(Accounts)]
  756. pub struct Withdraw<'info> {
  757. // Stake instance.
  758. registrar: ProgramAccount<'info, Registrar>,
  759. // Member.
  760. #[account(has_one = registrar, has_one = beneficiary)]
  761. member: ProgramAccount<'info, Member>,
  762. #[account(signer)]
  763. beneficiary: AccountInfo<'info>,
  764. #[account(mut, constraint = vault.to_account_info().key == &member.balances.vault)]
  765. vault: CpiAccount<'info, TokenAccount>,
  766. #[account(
  767. seeds = [registrar.to_account_info().key.as_ref(), member.to_account_info().key.as_ref()],
  768. bump = member.nonce,
  769. )]
  770. member_signer: AccountInfo<'info>,
  771. // Receiver.
  772. #[account(mut)]
  773. depositor: AccountInfo<'info>,
  774. // Misc.
  775. #[account(constraint = token_program.key == &token::ID)]
  776. token_program: AccountInfo<'info>,
  777. }
  778. #[derive(Accounts)]
  779. pub struct WithdrawLocked<'info> {
  780. // Lockup whitelist relay interface.
  781. #[account(
  782. constraint = vesting.to_account_info().owner == &registry.lockup_program,
  783. constraint = vesting.beneficiary == member.beneficiary,
  784. )]
  785. vesting: CpiAccount<'info, Vesting>,
  786. #[account(mut, constraint = vesting_vault.key == &vesting.vault)]
  787. vesting_vault: AccountInfo<'info>,
  788. #[account(signer)]
  789. vesting_signer: AccountInfo<'info>,
  790. #[account(constraint = token_program.key == &token::ID)]
  791. token_program: AccountInfo<'info>,
  792. #[account(
  793. mut,
  794. constraint = member_vault.to_account_info().key == &member.balances_locked.vault
  795. )]
  796. member_vault: CpiAccount<'info, TokenAccount>,
  797. #[account(
  798. seeds = [registrar.to_account_info().key.as_ref(), member.to_account_info().key.as_ref()],
  799. bump = member.nonce,
  800. )]
  801. member_signer: AccountInfo<'info>,
  802. // Program specific.
  803. registry: ProgramState<'info, Registry>,
  804. registrar: ProgramAccount<'info, Registrar>,
  805. #[account(has_one = registrar, has_one = beneficiary)]
  806. member: ProgramAccount<'info, Member>,
  807. #[account(signer)]
  808. beneficiary: AccountInfo<'info>,
  809. }
  810. #[derive(Accounts)]
  811. pub struct DropReward<'info> {
  812. // Staking instance.
  813. #[account(has_one = reward_event_q, has_one = pool_mint)]
  814. registrar: ProgramAccount<'info, Registrar>,
  815. #[account(mut)]
  816. reward_event_q: ProgramAccount<'info, RewardQueue>,
  817. pool_mint: CpiAccount<'info, Mint>,
  818. // Vendor.
  819. #[account(zero)]
  820. vendor: ProgramAccount<'info, RewardVendor>,
  821. #[account(mut)]
  822. vendor_vault: CpiAccount<'info, TokenAccount>,
  823. // Depositor.
  824. #[account(mut)]
  825. depositor: AccountInfo<'info>,
  826. #[account(signer)]
  827. depositor_authority: AccountInfo<'info>,
  828. // Misc.
  829. #[account(constraint = token_program.key == &token::ID)]
  830. token_program: AccountInfo<'info>,
  831. clock: Sysvar<'info, Clock>,
  832. }
  833. impl<'info> DropReward<'info> {
  834. fn accounts(ctx: &Context<DropReward>, nonce: u8) -> Result<()> {
  835. let vendor_signer = Pubkey::create_program_address(
  836. &[
  837. ctx.accounts.registrar.to_account_info().key.as_ref(),
  838. ctx.accounts.vendor.to_account_info().key.as_ref(),
  839. &[nonce],
  840. ],
  841. ctx.program_id,
  842. )
  843. .map_err(|_| ErrorCode::InvalidNonce)?;
  844. if vendor_signer != ctx.accounts.vendor_vault.owner {
  845. return Err(ErrorCode::InvalidVaultOwner.into());
  846. }
  847. Ok(())
  848. }
  849. }
  850. #[derive(Accounts)]
  851. pub struct ClaimReward<'info> {
  852. cmn: ClaimRewardCommon<'info>,
  853. // Account to send reward to.
  854. #[account(mut)]
  855. to: AccountInfo<'info>,
  856. }
  857. #[derive(Accounts)]
  858. pub struct ClaimRewardLocked<'info> {
  859. cmn: ClaimRewardCommon<'info>,
  860. registry: ProgramState<'info, Registry>,
  861. #[account("lockup_program.key == &registry.lockup_program")]
  862. lockup_program: AccountInfo<'info>,
  863. }
  864. // Accounts common to both claim reward locked/unlocked instructions.
  865. #[derive(Accounts)]
  866. pub struct ClaimRewardCommon<'info> {
  867. // Stake instance.
  868. registrar: ProgramAccount<'info, Registrar>,
  869. // Member.
  870. #[account(mut, has_one = registrar, has_one = beneficiary)]
  871. member: ProgramAccount<'info, Member>,
  872. #[account(signer)]
  873. beneficiary: AccountInfo<'info>,
  874. #[account(constraint = BalanceSandbox::from(&balances) == member.balances)]
  875. balances: BalanceSandboxAccounts<'info>,
  876. #[account(constraint = BalanceSandbox::from(&balances_locked) == member.balances_locked)]
  877. balances_locked: BalanceSandboxAccounts<'info>,
  878. // Vendor.
  879. #[account(has_one = registrar, has_one = vault)]
  880. vendor: ProgramAccount<'info, RewardVendor>,
  881. #[account(mut)]
  882. vault: AccountInfo<'info>,
  883. #[account(
  884. seeds = [registrar.to_account_info().key.as_ref(), vendor.to_account_info().key.as_ref()],
  885. bump = vendor.nonce,
  886. )]
  887. vendor_signer: AccountInfo<'info>,
  888. // Misc.
  889. #[account(constraint = token_program.key == &token::ID)]
  890. token_program: AccountInfo<'info>,
  891. clock: Sysvar<'info, Clock>,
  892. }
  893. #[derive(Accounts)]
  894. pub struct ExpireReward<'info> {
  895. // Staking instance globals.
  896. registrar: ProgramAccount<'info, Registrar>,
  897. // Vendor.
  898. #[account(mut, has_one = registrar, has_one = vault, has_one = expiry_receiver)]
  899. vendor: ProgramAccount<'info, RewardVendor>,
  900. #[account(mut)]
  901. vault: CpiAccount<'info, TokenAccount>,
  902. #[account(
  903. seeds = [registrar.to_account_info().key.as_ref(), vendor.to_account_info().key.as_ref()],
  904. bump = vendor.nonce
  905. )]
  906. vendor_signer: AccountInfo<'info>,
  907. // Receiver.
  908. #[account(signer)]
  909. expiry_receiver: AccountInfo<'info>,
  910. #[account(mut)]
  911. expiry_receiver_token: AccountInfo<'info>,
  912. // Misc.
  913. #[account(constraint = token_program.key == &token::ID)]
  914. token_program: AccountInfo<'info>,
  915. clock: Sysvar<'info, Clock>,
  916. }
  917. #[account]
  918. pub struct Registrar {
  919. /// Priviledged account.
  920. pub authority: Pubkey,
  921. /// Nonce to derive the program-derived address owning the vaults.
  922. pub nonce: u8,
  923. /// Number of seconds that must pass for a withdrawal to complete.
  924. pub withdrawal_timelock: i64,
  925. /// Global event queue for reward vendoring.
  926. pub reward_event_q: Pubkey,
  927. /// Mint of the tokens that can be staked.
  928. pub mint: Pubkey,
  929. /// Staking pool token mint.
  930. pub pool_mint: Pubkey,
  931. /// The amount of tokens (not decimal) that must be staked to get a single
  932. /// staking pool token.
  933. pub stake_rate: u64,
  934. }
  935. #[account]
  936. pub struct Member {
  937. /// Registrar the member belongs to.
  938. pub registrar: Pubkey,
  939. /// The effective owner of the Member account.
  940. pub beneficiary: Pubkey,
  941. /// Arbitrary metadata account owned by any program.
  942. pub metadata: Pubkey,
  943. /// Sets of balances owned by the Member.
  944. pub balances: BalanceSandbox,
  945. /// Locked balances owned by the Member.
  946. pub balances_locked: BalanceSandbox,
  947. /// Next position in the rewards event queue to process.
  948. pub rewards_cursor: u32,
  949. /// The clock timestamp of the last time this account staked or switched
  950. /// entities. Used as a proof to reward vendors that the Member account
  951. /// was staked at a given point in time.
  952. pub last_stake_ts: i64,
  953. /// Signer nonce.
  954. pub nonce: u8,
  955. }
  956. // BalanceSandbox defines isolated funds that can only be deposited/withdrawn
  957. // into the program.
  958. //
  959. // Once controlled by the program, the associated `Member` account's beneficiary
  960. // can send funds to/from any of the accounts within the sandbox, e.g., to
  961. // stake.
  962. #[derive(AnchorSerialize, AnchorDeserialize, Default, Debug, Clone, PartialEq)]
  963. pub struct BalanceSandbox {
  964. // Staking pool token.
  965. pub spt: Pubkey,
  966. // Free balance (deposit) vaults.
  967. pub vault: Pubkey,
  968. // Stake vaults.
  969. pub vault_stake: Pubkey,
  970. // Pending withdrawal vaults.
  971. pub vault_pw: Pubkey,
  972. }
  973. #[account]
  974. pub struct PendingWithdrawal {
  975. /// Registrar this account belongs to.
  976. pub registrar: Pubkey,
  977. /// Member this account belongs to.
  978. pub member: Pubkey,
  979. /// One time token. True if the withdrawal has been completed.
  980. pub burned: bool,
  981. /// The pool being withdrawn from.
  982. pub pool: Pubkey,
  983. /// Unix timestamp when this account was initialized.
  984. pub start_ts: i64,
  985. /// Timestamp when the pending withdrawal completes.
  986. pub end_ts: i64,
  987. /// The number of tokens redeemed from the staking pool.
  988. pub amount: u64,
  989. /// True if the withdrawal applies to locked balances.
  990. pub locked: bool,
  991. }
  992. #[account]
  993. pub struct RewardQueue {
  994. // Invariant: index is position of the next available slot.
  995. head: u32,
  996. // Invariant: index is position of the first (oldest) taken slot.
  997. // Invariant: head == tail => queue is initialized.
  998. // Invariant: index_of(head + 1) == index_of(tail) => queue is full.
  999. tail: u32,
  1000. // Although a vec is used, the size is immutable.
  1001. events: Vec<RewardEvent>,
  1002. }
  1003. impl RewardQueue {
  1004. pub fn append(&mut self, event: RewardEvent) -> Result<u32> {
  1005. let cursor = self.head;
  1006. // Insert into next available slot.
  1007. let h_idx = self.index_of(self.head);
  1008. self.events[h_idx] = event;
  1009. // Update head and tail counters.
  1010. let is_full = self.index_of(self.head + 1) == self.index_of(self.tail);
  1011. if is_full {
  1012. self.tail += 1;
  1013. }
  1014. self.head += 1;
  1015. Ok(cursor)
  1016. }
  1017. pub fn index_of(&self, counter: u32) -> usize {
  1018. counter as usize % self.capacity()
  1019. }
  1020. pub fn capacity(&self) -> usize {
  1021. self.events.len()
  1022. }
  1023. pub fn get(&self, cursor: u32) -> &RewardEvent {
  1024. &self.events[cursor as usize % self.capacity()]
  1025. }
  1026. pub fn head(&self) -> u32 {
  1027. self.head
  1028. }
  1029. pub fn tail(&self) -> u32 {
  1030. self.tail
  1031. }
  1032. }
  1033. #[derive(Default, Clone, Copy, Debug, AnchorSerialize, AnchorDeserialize)]
  1034. pub struct RewardEvent {
  1035. vendor: Pubkey,
  1036. ts: i64,
  1037. locked: bool,
  1038. }
  1039. #[account]
  1040. pub struct RewardVendor {
  1041. pub registrar: Pubkey,
  1042. pub vault: Pubkey,
  1043. pub mint: Pubkey,
  1044. pub nonce: u8,
  1045. pub pool_token_supply: u64,
  1046. pub reward_event_q_cursor: u32,
  1047. pub start_ts: i64,
  1048. pub expiry_ts: i64,
  1049. pub expiry_receiver: Pubkey,
  1050. pub from: Pubkey,
  1051. pub total: u64,
  1052. pub expired: bool,
  1053. pub kind: RewardVendorKind,
  1054. }
  1055. #[derive(AnchorSerialize, AnchorDeserialize, Clone, PartialEq)]
  1056. pub enum RewardVendorKind {
  1057. Unlocked,
  1058. Locked {
  1059. start_ts: i64,
  1060. end_ts: i64,
  1061. period_count: u64,
  1062. },
  1063. }
  1064. #[error]
  1065. pub enum ErrorCode {
  1066. #[msg("The given reward queue has already been initialized.")]
  1067. RewardQAlreadyInitialized,
  1068. #[msg("The nonce given doesn't derive a valid program address.")]
  1069. InvalidNonce,
  1070. #[msg("Invalid pool mint authority")]
  1071. InvalidPoolMintAuthority,
  1072. #[msg("Member signer doesn't match the derived address.")]
  1073. InvalidMemberSigner,
  1074. #[msg("The given vault owner must match the signing depositor.")]
  1075. InvalidVaultDeposit,
  1076. #[msg("The signing depositor doesn't match either of the balance accounts")]
  1077. InvalidDepositor,
  1078. #[msg("The vault given does not match the vault expected.")]
  1079. InvalidVault,
  1080. #[msg("Invalid vault owner.")]
  1081. InvalidVaultOwner,
  1082. #[msg("An unknown error has occured.")]
  1083. Unknown,
  1084. #[msg("The unstake timelock has not yet expired.")]
  1085. UnstakeTimelock,
  1086. #[msg("Reward vendors must have at least one token unit per pool token")]
  1087. InsufficientReward,
  1088. #[msg("Reward expiry must be after the current clock timestamp.")]
  1089. InvalidExpiry,
  1090. #[msg("The reward vendor has been expired.")]
  1091. VendorExpired,
  1092. #[msg("This reward has already been processed.")]
  1093. CursorAlreadyProcessed,
  1094. #[msg("The account was not staked at the time of this reward.")]
  1095. NotStakedDuringDrop,
  1096. #[msg("The vendor is not yet eligible for expiry.")]
  1097. VendorNotYetExpired,
  1098. #[msg("Please collect your reward before otherwise using the program.")]
  1099. RewardsNeedsProcessing,
  1100. #[msg("Locked reward vendor expected but an unlocked vendor was given.")]
  1101. ExpectedLockedVendor,
  1102. #[msg("Unlocked reward vendor expected but a locked vendor was given.")]
  1103. ExpectedUnlockedVendor,
  1104. #[msg("Locked deposit from an invalid deposit authority.")]
  1105. InvalidVestingSigner,
  1106. #[msg("Locked rewards cannot be realized until one unstaked all tokens.")]
  1107. UnrealizedReward,
  1108. #[msg("The beneficiary doesn't match.")]
  1109. InvalidBeneficiary,
  1110. #[msg("The given member account does not match the realizor metadata.")]
  1111. InvalidRealizorMetadata,
  1112. #[msg("Invalid vesting schedule for the locked reward.")]
  1113. InvalidVestingSchedule,
  1114. #[msg("Please specify the correct authority for this program.")]
  1115. InvalidProgramAuthority,
  1116. }
  1117. impl<'a, 'b, 'c, 'info> From<&mut Deposit<'info>>
  1118. for CpiContext<'a, 'b, 'c, 'info, Transfer<'info>>
  1119. {
  1120. fn from(accounts: &mut Deposit<'info>) -> CpiContext<'a, 'b, 'c, 'info, Transfer<'info>> {
  1121. let cpi_accounts = Transfer {
  1122. from: accounts.depositor.clone(),
  1123. to: accounts.vault.to_account_info(),
  1124. authority: accounts.depositor_authority.clone(),
  1125. };
  1126. let cpi_program = accounts.token_program.clone();
  1127. CpiContext::new(cpi_program, cpi_accounts)
  1128. }
  1129. }
  1130. impl<'a, 'b, 'c, 'info> From<&mut DepositLocked<'info>>
  1131. for CpiContext<'a, 'b, 'c, 'info, Transfer<'info>>
  1132. {
  1133. fn from(accounts: &mut DepositLocked<'info>) -> CpiContext<'a, 'b, 'c, 'info, Transfer<'info>> {
  1134. let cpi_accounts = Transfer {
  1135. from: accounts.vesting_vault.clone(),
  1136. to: accounts.member_vault.to_account_info(),
  1137. authority: accounts.depositor_authority.clone(),
  1138. };
  1139. let cpi_program = accounts.token_program.clone();
  1140. CpiContext::new(cpi_program, cpi_accounts)
  1141. }
  1142. }
  1143. impl<'a, 'b, 'c, 'info> From<&mut DropReward<'info>>
  1144. for CpiContext<'a, 'b, 'c, 'info, Transfer<'info>>
  1145. {
  1146. fn from(accounts: &mut DropReward<'info>) -> CpiContext<'a, 'b, 'c, 'info, Transfer<'info>> {
  1147. let cpi_accounts = Transfer {
  1148. from: accounts.depositor.clone(),
  1149. to: accounts.vendor_vault.to_account_info(),
  1150. authority: accounts.depositor_authority.clone(),
  1151. };
  1152. let cpi_program = accounts.token_program.clone();
  1153. CpiContext::new(cpi_program, cpi_accounts)
  1154. }
  1155. }
  1156. impl<'info> From<&BalanceSandboxAccounts<'info>> for BalanceSandbox {
  1157. fn from(accs: &BalanceSandboxAccounts<'info>) -> Self {
  1158. Self {
  1159. spt: *accs.spt.to_account_info().key,
  1160. vault: *accs.vault.to_account_info().key,
  1161. vault_stake: *accs.vault_stake.to_account_info().key,
  1162. vault_pw: *accs.vault_pw.to_account_info().key,
  1163. }
  1164. }
  1165. }
  1166. fn reward_eligible(cmn: &ClaimRewardCommon) -> Result<()> {
  1167. let vendor = &cmn.vendor;
  1168. let member = &cmn.member;
  1169. if vendor.expired {
  1170. return Err(ErrorCode::VendorExpired.into());
  1171. }
  1172. if member.rewards_cursor > vendor.reward_event_q_cursor {
  1173. return Err(ErrorCode::CursorAlreadyProcessed.into());
  1174. }
  1175. if member.last_stake_ts > vendor.start_ts {
  1176. return Err(ErrorCode::NotStakedDuringDrop.into());
  1177. }
  1178. Ok(())
  1179. }
  1180. // Asserts the user calling the `Stake` instruction has no rewards available
  1181. // in the reward queue.
  1182. pub fn no_available_rewards<'info>(
  1183. reward_q: &ProgramAccount<'info, RewardQueue>,
  1184. member: &ProgramAccount<'info, Member>,
  1185. balances: &BalanceSandboxAccounts<'info>,
  1186. balances_locked: &BalanceSandboxAccounts<'info>,
  1187. ) -> Result<()> {
  1188. let mut cursor = member.rewards_cursor;
  1189. // If the member's cursor is less then the tail, then the ring buffer has
  1190. // overwritten those entries, so jump to the tail.
  1191. let tail = reward_q.tail();
  1192. if cursor < tail {
  1193. cursor = tail;
  1194. }
  1195. while cursor < reward_q.head() {
  1196. let r_event = reward_q.get(cursor);
  1197. if member.last_stake_ts < r_event.ts {
  1198. if balances.spt.amount > 0 || balances_locked.spt.amount > 0 {
  1199. return Err(ErrorCode::RewardsNeedsProcessing.into());
  1200. }
  1201. }
  1202. cursor += 1;
  1203. }
  1204. Ok(())
  1205. }