|
9 | 9 | pda::find_metadata_account, |
10 | 10 | state::DataV2, |
11 | 11 | }, |
12 | | - instruction::SinglePoolInstruction, |
| 12 | + instruction::{self as svsp_instruction, SinglePoolInstruction}, |
13 | 13 | state::{SinglePool, SinglePoolAccountType}, |
14 | | - MINT_DECIMALS, PERPETUAL_NEW_WARMUP_COOLDOWN_RATE_EPOCH, PHANTOM_TOKEN_AMOUNT, |
15 | | - POOL_MINT_AUTHORITY_PREFIX, POOL_MINT_PREFIX, POOL_MPL_AUTHORITY_PREFIX, |
16 | | - POOL_ONRAMP_PREFIX, POOL_PREFIX, POOL_STAKE_AUTHORITY_PREFIX, POOL_STAKE_PREFIX, |
17 | | - VOTE_STATE_AUTHORIZED_WITHDRAWER_END, VOTE_STATE_AUTHORIZED_WITHDRAWER_START, |
18 | | - VOTE_STATE_DISCRIMINATOR_END, |
| 14 | + DEPOSIT_SOL_FEE_BPS, MAX_BPS, MINT_DECIMALS, PERPETUAL_NEW_WARMUP_COOLDOWN_RATE_EPOCH, |
| 15 | + PHANTOM_TOKEN_AMOUNT, POOL_MINT_AUTHORITY_PREFIX, POOL_MINT_PREFIX, |
| 16 | + POOL_MPL_AUTHORITY_PREFIX, POOL_ONRAMP_PREFIX, POOL_PREFIX, POOL_STAKE_AUTHORITY_PREFIX, |
| 17 | + POOL_STAKE_PREFIX, VOTE_STATE_AUTHORIZED_WITHDRAWER_END, |
| 18 | + VOTE_STATE_AUTHORIZED_WITHDRAWER_START, VOTE_STATE_DISCRIMINATOR_END, |
19 | 19 | }, |
20 | 20 | borsh::BorshDeserialize, |
21 | 21 | solana_account_info::{next_account_info, AccountInfo}, |
22 | 22 | solana_borsh::v1::try_from_slice_unchecked, |
23 | 23 | solana_clock::Clock, |
24 | | - solana_cpi::invoke_signed, |
| 24 | + solana_cpi::{invoke, invoke_signed}, |
25 | 25 | solana_msg::msg, |
26 | 26 | solana_native_token::LAMPORTS_PER_SOL, |
27 | 27 | solana_program_entrypoint::ProgramResult, |
@@ -1544,6 +1544,160 @@ impl Processor { |
1544 | 1544 | Ok(()) |
1545 | 1545 | } |
1546 | 1546 |
|
| 1547 | + fn process_deposit_sol( |
| 1548 | + program_id: &Pubkey, |
| 1549 | + accounts: &[AccountInfo], |
| 1550 | + deposit_amount: u64, |
| 1551 | + ) -> ProgramResult { |
| 1552 | + let account_info_iter = &mut accounts.iter(); |
| 1553 | + let vote_account_info = next_account_info(account_info_iter)?; |
| 1554 | + let pool_info = next_account_info(account_info_iter)?; |
| 1555 | + let pool_stake_info = next_account_info(account_info_iter)?; |
| 1556 | + let pool_onramp_info = next_account_info(account_info_iter)?; |
| 1557 | + let pool_mint_info = next_account_info(account_info_iter)?; |
| 1558 | + let pool_stake_authority_info = next_account_info(account_info_iter)?; |
| 1559 | + let pool_mint_authority_info = next_account_info(account_info_iter)?; |
| 1560 | + let user_lamport_account_info = next_account_info(account_info_iter)?; |
| 1561 | + let user_token_account_info = next_account_info(account_info_iter)?; |
| 1562 | + let clock_info = next_account_info(account_info_iter)?; |
| 1563 | + let clock = &Clock::from_account_info(clock_info)?; |
| 1564 | + let stake_history_info = next_account_info(account_info_iter)?; |
| 1565 | + let stake_config_info = next_account_info(account_info_iter)?; |
| 1566 | + let system_program_info = next_account_info(account_info_iter)?; |
| 1567 | + let token_program_info = next_account_info(account_info_iter)?; |
| 1568 | + let stake_program_info = next_account_info(account_info_iter)?; |
| 1569 | + |
| 1570 | + let rent = Rent::get()?; |
| 1571 | + let stake_history = &StakeHistorySysvar(clock.epoch); |
| 1572 | + |
| 1573 | + check_vote_account(vote_account_info)?; |
| 1574 | + check_pool_address(program_id, vote_account_info.key, pool_info.key)?; |
| 1575 | + |
| 1576 | + SinglePool::from_account_info(pool_info, program_id)?; |
| 1577 | + |
| 1578 | + check_pool_stake_address(program_id, pool_info.key, pool_stake_info.key)?; |
| 1579 | + check_pool_onramp_address(program_id, pool_info.key, pool_onramp_info.key)?; |
| 1580 | + let token_supply = check_pool_mint_with_supply(program_id, pool_info.key, pool_mint_info)?; |
| 1581 | + check_pool_stake_authority_address( |
| 1582 | + program_id, |
| 1583 | + pool_info.key, |
| 1584 | + pool_stake_authority_info.key, |
| 1585 | + )?; |
| 1586 | + let mint_authority_bump_seed = check_pool_mint_authority_address( |
| 1587 | + program_id, |
| 1588 | + pool_info.key, |
| 1589 | + pool_mint_authority_info.key, |
| 1590 | + )?; |
| 1591 | + check_system_program(system_program_info.key)?; |
| 1592 | + check_token_program(token_program_info.key)?; |
| 1593 | + check_stake_program(stake_program_info.key)?; |
| 1594 | + |
| 1595 | + if deposit_amount == 0 { |
| 1596 | + return Err(SinglePoolError::DepositTooSmall.into()); |
| 1597 | + } |
| 1598 | + |
| 1599 | + // we require the pool to be fully active for this instruction to minimize complexity |
| 1600 | + { |
| 1601 | + let (_, pool_stake_state) = get_stake_state(pool_stake_info)?; |
| 1602 | + let pool_stake_status = pool_stake_state |
| 1603 | + .delegation |
| 1604 | + .stake_activating_and_deactivating( |
| 1605 | + clock.epoch, |
| 1606 | + stake_history, |
| 1607 | + PERPETUAL_NEW_WARMUP_COOLDOWN_RATE_EPOCH, |
| 1608 | + ); |
| 1609 | + |
| 1610 | + if !is_stake_fully_active(&pool_stake_status) { |
| 1611 | + return Err(SinglePoolError::WrongStakeState.into()); |
| 1612 | + } |
| 1613 | + }; |
| 1614 | + |
| 1615 | + // we require onramp to exist, though we dont care what state its in |
| 1616 | + match deserialize_stake(pool_onramp_info) { |
| 1617 | + Ok(StakeStateV2::Initialized(_)) | Ok(StakeStateV2::Stake(_, _, _)) => (), |
| 1618 | + _ => return Err(SinglePoolError::OnRampDoesntExist.into()), |
| 1619 | + } |
| 1620 | + |
| 1621 | + // deposit source must be a system account for transfer to succeed |
| 1622 | + if !user_lamport_account_info.is_signer |
| 1623 | + || user_lamport_account_info.owner != &system_program::id() |
| 1624 | + || user_lamport_account_info.lamports() < deposit_amount |
| 1625 | + { |
| 1626 | + return Err(SinglePoolError::InvalidDepositSolSource.into()); |
| 1627 | + } |
| 1628 | + |
| 1629 | + let pre_total_nev = pool_net_asset_value(pool_stake_info, pool_onramp_info, &rent); |
| 1630 | + let pre_onramp_lamports = pool_onramp_info.lamports(); |
| 1631 | + |
| 1632 | + // transfer sol to pool onramp |
| 1633 | + invoke( |
| 1634 | + &system_instruction::transfer( |
| 1635 | + user_lamport_account_info.key, |
| 1636 | + pool_onramp_info.key, |
| 1637 | + deposit_amount, |
| 1638 | + ), |
| 1639 | + &[user_lamport_account_info.clone(), pool_onramp_info.clone()], |
| 1640 | + )?; |
| 1641 | + |
| 1642 | + // sanity, should be impossible |
| 1643 | + if pool_onramp_info.lamports() != pre_onramp_lamports.saturating_add(deposit_amount) { |
| 1644 | + return Err(SinglePoolError::UnexpectedMathError.into()); |
| 1645 | + } |
| 1646 | + |
| 1647 | + let new_pool_tokens = { |
| 1648 | + let raw_tokens = calculate_deposit_amount(token_supply, pre_total_nev, deposit_amount) |
| 1649 | + .ok_or(SinglePoolError::UnexpectedMathError)?; |
| 1650 | + |
| 1651 | + // we round division down and reject deposits too small to generate a fee |
| 1652 | + // this is to avoid pathological cases where eg someone deposits 2 lamps and pays 50% |
| 1653 | + let deposit_sol_fee = raw_tokens |
| 1654 | + .checked_mul(DEPOSIT_SOL_FEE_BPS) |
| 1655 | + .and_then(|n| n.checked_div(MAX_BPS)) |
| 1656 | + .ok_or(SinglePoolError::UnexpectedMathError)?; |
| 1657 | + |
| 1658 | + // because we abort on no fee, we know new_pool_tokens gt 0 |
| 1659 | + if deposit_sol_fee == 0 { |
| 1660 | + return Err(SinglePoolError::DepositTooSmall.into()); |
| 1661 | + } |
| 1662 | + |
| 1663 | + raw_tokens.saturating_sub(deposit_sol_fee) |
| 1664 | + }; |
| 1665 | + |
| 1666 | + // sanity, should be impossible per above |
| 1667 | + if new_pool_tokens == 0 { |
| 1668 | + return Err(SinglePoolError::UnexpectedMathError.into()); |
| 1669 | + } |
| 1670 | + |
| 1671 | + // mint tokens to the user corresponding to their sol deposit |
| 1672 | + Self::token_mint_to( |
| 1673 | + pool_info.key, |
| 1674 | + token_program_info.clone(), |
| 1675 | + pool_mint_info.clone(), |
| 1676 | + user_token_account_info.clone(), |
| 1677 | + pool_mint_authority_info.clone(), |
| 1678 | + mint_authority_bump_seed, |
| 1679 | + new_pool_tokens, |
| 1680 | + )?; |
| 1681 | + |
| 1682 | + // replenish to delegate the deposit. this safely returns Ok if onramp doesnt meet minimum delegation |
| 1683 | + invoke( |
| 1684 | + &svsp_instruction::replenish_pool(program_id, vote_account_info.key), |
| 1685 | + &[ |
| 1686 | + vote_account_info.clone(), |
| 1687 | + pool_info.clone(), |
| 1688 | + pool_stake_info.clone(), |
| 1689 | + pool_onramp_info.clone(), |
| 1690 | + pool_stake_authority_info.clone(), |
| 1691 | + clock_info.clone(), |
| 1692 | + stake_history_info.clone(), |
| 1693 | + stake_config_info.clone(), |
| 1694 | + stake_program_info.clone(), |
| 1695 | + ], |
| 1696 | + )?; |
| 1697 | + |
| 1698 | + Ok(()) |
| 1699 | + } |
| 1700 | + |
1547 | 1701 | /// Processes [Instruction](enum.Instruction.html). |
1548 | 1702 | pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], input: &[u8]) -> ProgramResult { |
1549 | 1703 | let instruction = SinglePoolInstruction::try_from_slice(input)?; |
@@ -1584,9 +1738,9 @@ impl Processor { |
1584 | 1738 | msg!("Instruction: InitializePoolOnRamp"); |
1585 | 1739 | Self::process_initialize_pool_onramp(program_id, accounts) |
1586 | 1740 | } |
1587 | | - SinglePoolInstruction::DepositSol { lamports: _ } => { |
1588 | | - msg!("Instruction: DepositSol (NOT IMPLEMENTED)"); |
1589 | | - Err(ProgramError::InvalidInstructionData) |
| 1741 | + SinglePoolInstruction::DepositSol { lamports } => { |
| 1742 | + msg!("Instruction: DepositSol"); |
| 1743 | + Self::process_deposit_sol(program_id, accounts, lamports) |
1590 | 1744 | } |
1591 | 1745 | } |
1592 | 1746 | } |
|
0 commit comments