swap.rs 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. use spl_math::uint::U256;
  2. use steel::*;
  3. use token_swap_api::prelude::*;
  4. pub fn process_swap(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult {
  5. // Load accounts.
  6. let [payer_info, trader_info, amm_info, pool_info, pool_authority_info, mint_a_info, mint_b_info, pool_account_a_info, pool_account_b_info, trader_account_a_info, trader_account_b_info, token_program, system_program, associated_token_program] =
  7. accounts
  8. else {
  9. return Err(ProgramError::NotEnoughAccountKeys);
  10. };
  11. let args = Swap::try_from_bytes(data)?;
  12. let swap_a: bool = args.swap_a == 1;
  13. let input_amount = u64::from_le_bytes(args.input_amount);
  14. let min_output_amount = u64::from_le_bytes(args.min_output_amount);
  15. // Check payer account is signer and program is the correct program.
  16. payer_info.is_signer()?;
  17. token_program.is_program(&spl_token::ID)?;
  18. system_program.is_program(&system_program::ID)?;
  19. associated_token_program.is_program(&ASSOCIATED_TOKEN_PROGRAM_ID)?;
  20. // check if depositor is signer
  21. trader_info.is_signer()?;
  22. // Verify mint_a and mint_b is a mint account.
  23. let _mint_a = mint_a_info.as_mint()?;
  24. let _mint_b = mint_b_info.as_mint()?;
  25. // validate pool account
  26. if pool_info.data_is_empty() {
  27. return Err(TokenSwapError::AccountIsNotExisted.into());
  28. }
  29. validate_pool_account(pool_info, *mint_a_info.key, *mint_b_info.key)?;
  30. let pool_info_data = pool_info.as_account_mut::<Pool>(&token_swap_api::ID)?;
  31. // validate pool authority
  32. validate_pool_authority(
  33. pool_info_data,
  34. pool_authority_info,
  35. *mint_a_info.key,
  36. *mint_b_info.key,
  37. )?;
  38. // Check amm account is owned by token_swap_api::ID.
  39. amm_info.has_owner(&token_swap_api::ID)?;
  40. // // validate pool_account_a_info, pool_account_b_info
  41. let pool_account_a = pool_account_a_info
  42. .is_writable()?
  43. .as_associated_token_account(pool_authority_info.key, mint_a_info.key)?;
  44. let pool_account_b = pool_account_b_info
  45. .is_writable()?
  46. .as_associated_token_account(pool_authority_info.key, mint_b_info.key)?;
  47. // check if trader_account_a and trader_account_b is exists
  48. if trader_account_a_info.data_is_empty() {
  49. // Create the depositor's liquidity account if it does not exist
  50. create_associated_token_account(
  51. payer_info,
  52. trader_info,
  53. trader_account_a_info,
  54. mint_a_info,
  55. system_program,
  56. token_program,
  57. associated_token_program,
  58. )?;
  59. }
  60. if trader_account_b_info.data_is_empty() {
  61. // Create the depositor's liquidity account if it does not exist
  62. create_associated_token_account(
  63. payer_info,
  64. trader_info,
  65. trader_account_b_info,
  66. mint_b_info,
  67. system_program,
  68. token_program,
  69. associated_token_program,
  70. )?;
  71. }
  72. let trader_account_a =
  73. trader_account_a_info.as_associated_token_account(trader_info.key, mint_a_info.key)?;
  74. let trader_account_b =
  75. trader_account_b_info.as_associated_token_account(trader_info.key, mint_b_info.key)?;
  76. // Prevent depositing assets the depositor does not own
  77. let input = if swap_a && input_amount > trader_account_a.amount {
  78. trader_account_a.amount
  79. } else if !swap_a && input_amount > trader_account_b.amount {
  80. trader_account_b.amount
  81. } else {
  82. input_amount
  83. };
  84. // Apply trading fee, used to compute the output
  85. let amm = amm_info.as_account_mut::<Amm>(&token_swap_api::ID)?;
  86. let fee = u16::from_le_bytes(amm.fee);
  87. let taxed_input = input - input * (fee as u64) / 10000;
  88. let output = if swap_a {
  89. U256::from(taxed_input)
  90. .checked_mul(U256::from(pool_account_b.amount))
  91. .unwrap()
  92. .checked_div(
  93. U256::from(pool_account_a.amount)
  94. .checked_add(U256::from(taxed_input))
  95. .unwrap(),
  96. )
  97. .unwrap()
  98. } else {
  99. U256::from(taxed_input)
  100. .checked_mul(U256::from(pool_account_a.amount))
  101. .unwrap()
  102. .checked_div(
  103. U256::from(pool_account_b.amount)
  104. .checked_add(U256::from(taxed_input))
  105. .unwrap(),
  106. )
  107. .unwrap()
  108. }
  109. .as_u64();
  110. if output < min_output_amount {
  111. return Err(TokenSwapError::OutputTooSmall.into());
  112. }
  113. let pool_authority_seeds = &[
  114. pool_info_data.amm.as_ref(),
  115. pool_info_data.mint_a.as_ref(),
  116. pool_info_data.mint_b.as_ref(),
  117. AUTHORITY_SEED,
  118. ];
  119. // // Compute the invariant before the trade
  120. let invariant = pool_account_a.amount * pool_account_b.amount;
  121. if swap_a {
  122. transfer(
  123. trader_info,
  124. trader_account_a_info,
  125. pool_account_a_info,
  126. token_program,
  127. input,
  128. )?;
  129. transfer_signed_with_bump(
  130. pool_authority_info,
  131. pool_account_b_info,
  132. trader_account_b_info,
  133. token_program,
  134. output,
  135. pool_authority_seeds,
  136. pool_info_data.pool_authority_bump,
  137. )?;
  138. } else {
  139. transfer_signed_with_bump(
  140. pool_authority_info,
  141. pool_account_a_info,
  142. trader_account_a_info,
  143. token_program,
  144. input,
  145. pool_authority_seeds,
  146. pool_info_data.pool_authority_bump,
  147. )?;
  148. transfer(
  149. trader_info,
  150. trader_account_b_info,
  151. pool_account_b_info,
  152. token_program,
  153. output,
  154. )?;
  155. }
  156. let pool_account_a = pool_account_a_info
  157. .as_associated_token_account(pool_authority_info.key, mint_a_info.key)?;
  158. let pool_account_b = pool_account_b_info
  159. .as_associated_token_account(pool_authority_info.key, mint_b_info.key)?;
  160. if invariant > pool_account_a.amount * pool_account_b.amount {
  161. return Err(TokenSwapError::InvariantViolated.into());
  162. }
  163. Ok(())
  164. }