access-protocol 0.7.15

Access Protocol
Documentation
use solana_program::clock::SECONDS_PER_DAY;
use solana_sdk::signer::Signer;

use access_protocol::state::Tag::BondV2Account;

use crate::common::test_runner::TestRunner;

pub mod common;

#[tokio::test]
async fn v2_bonds() {
    // Setup the token + basic accounts
    let mut tr = TestRunner::new(1_000_000).await.unwrap();

    // ---------------------------------------------------------------------------------------------
    // V1 bond creation should be deprecated
    // ---------------------------------------------------------------------------------------------
    {
        let pool_owner = tr.create_user_with_ata().await.unwrap();
        let bond_creator = tr.create_user_with_ata().await.unwrap();
        tr.create_bond(
            &pool_owner.pubkey(),
            &bond_creator.pubkey(),
            10_000,
            1,
            1,
            1,
        )
            .await
            .unwrap_err();
    }
    // ---------------------------------------------------------------------------------------------
    // Unlockable bond
    // ---------------------------------------------------------------------------------------------
    {
        // Create users
        let pool_owner = tr.create_user_with_ata().await.unwrap();
        let bond_creator = tr.create_user_with_ata().await.unwrap();
        let bond_recipient = tr.create_user_with_ata().await.unwrap();
        // Mint to staker
        tr.mint(&bond_creator.pubkey(), 100_000).await.unwrap();
        // Create stake pool
        tr.create_pool(&pool_owner, 10_000)
            .await
            .unwrap();
        // Activate stake pool
        tr.activate_stake_pool(&pool_owner.pubkey()).await.unwrap();

        let current_time = tr.get_current_time().await;
        let unlock_date = current_time + 5 * SECONDS_PER_DAY as i64;
        let bond_amount = 20_000;

        let token_stats = tr.token_stats().await.unwrap();
        let prev_supply = token_stats.supply;

        // Create bond
        tr.create_bond_v2(
            &bond_recipient.pubkey(),
            &pool_owner.pubkey(),
            Some(unlock_date),
        )
            .await
            .unwrap();

        // Add to bond
        tr.add_to_bond_v2(
            &bond_creator,
            &bond_recipient.pubkey(),
            &pool_owner.pubkey(),
            bond_amount,
            Some(unlock_date),
        )
            .await
            .unwrap();

        let staker_stats = tr.staker_stats(bond_creator.pubkey()).await.unwrap();
        assert_eq!(
            staker_stats.balance,
            (100_000 - bond_amount) - (bond_amount as f64 * tr.get_protocol_fees().await / 100.0).ceil() as u64
        );
        let pool_stats = tr.pool_stats(pool_owner.pubkey()).await.unwrap();
        assert_eq!(pool_stats.header.total_staked, bond_amount);
        assert_eq!(pool_stats.vault, bond_amount);
        let token_stats = tr.token_stats().await.unwrap();
        assert_eq!(token_stats.supply, prev_supply);
        let central_state_stats = tr.central_state_stats().await.unwrap();
        assert_eq!(central_state_stats.account.total_staked, bond_amount);
        let bond = tr
            .bond_v2_stats(
                bond_recipient.pubkey(),
                pool_owner.pubkey(),
                Some(unlock_date),
            )
            .await
            .unwrap();
        assert_eq!(bond.tag, BondV2Account);
        assert_eq!(bond.unlock_timestamp, Some(unlock_date));
        assert_eq!(bond.pool, tr.get_pool_pda(&pool_owner.pubkey()));
        assert_eq!(bond.amount, bond_amount);
        assert_eq!(bond.owner, bond_recipient.pubkey());
        assert_eq!(bond.last_claimed_offset, 0);
        assert_eq!(bond.pool_minimum_at_creation, 10_000);

        // Add to bond
        let add_amount = 10;
        tr.add_to_bond_v2(
            &bond_creator,
            &bond_recipient.pubkey(),
            &pool_owner.pubkey(),
            add_amount,
            Some(unlock_date),
        )
            .await
            .unwrap();

        let staker_stats = tr.staker_stats(bond_creator.pubkey()).await.unwrap();
        assert_eq!(
            staker_stats.balance,
            100_000 - (bond_amount + add_amount) - ((bond_amount + add_amount) as f64 * tr.get_protocol_fees().await / 100.0).ceil() as u64
        );
        let pool_stats = tr.pool_stats(pool_owner.pubkey()).await.unwrap();
        assert_eq!(pool_stats.header.total_staked, (bond_amount + add_amount));
        assert_eq!(pool_stats.vault, (bond_amount + add_amount));
        let central_state_stats = tr.central_state_stats().await.unwrap();
        assert_eq!(central_state_stats.account.total_staked, (bond_amount + add_amount));
        let bond = tr
            .bond_v2_stats(
                bond_recipient.pubkey(),
                pool_owner.pubkey(),
                Some(unlock_date),
            )
            .await
            .unwrap();
        assert_eq!(bond.tag, BondV2Account);
        assert_eq!(bond.unlock_timestamp, Some(unlock_date));
        assert_eq!(bond.pool, tr.get_pool_pda(&pool_owner.pubkey()));
        assert_eq!(bond.amount, (bond_amount + add_amount));
        assert_eq!(bond.owner, bond_recipient.pubkey());
        assert_eq!(bond.last_claimed_offset, 0);
        assert_eq!(bond.pool_minimum_at_creation, 10_000);

        // Claim zero rewards
        let recipient_stats = tr.staker_stats(bond_recipient.pubkey()).await.unwrap();
        assert_eq!(recipient_stats.balance, 0);
        tr.claim_bond_v2_rewards(&bond_recipient, &pool_owner.pubkey(), Some(unlock_date))
            .await
            .unwrap_err();
        let recipient_stats = tr.staker_stats(bond_recipient.pubkey()).await.unwrap();
        assert_eq!(recipient_stats.balance, 0);

        // Claim rewards
        _ = tr.sleep(SECONDS_PER_DAY).await;
        tr.crank_pool(&pool_owner.pubkey()).await.unwrap();
        tr.claim_bond_v2_rewards(&bond_recipient, &pool_owner.pubkey(), Some(unlock_date))
            .await
            .unwrap();
        let recipient_stats = tr.staker_stats(bond_recipient.pubkey()).await.unwrap();
        assert_eq!(recipient_stats.balance, 500_000);

        // Try unlocking - shouldn't be possible
        tr.unlock_bond_v2_tokens(&bond_recipient, &pool_owner.pubkey(), Some(unlock_date))
            .await
            .unwrap_err();

        // Move 5 days to the future
        _ = tr.sleep(5 * SECONDS_PER_DAY).await;
        tr.crank_pool(&pool_owner.pubkey()).await.unwrap();
        // Unlocking should not be possible before reward claim
        tr.unlock_bond_v2_tokens(&bond_recipient, &pool_owner.pubkey(), Some(unlock_date))
            .await
            .unwrap_err();
        _ = tr.sleep(1).await;
        // Claim rewards
        tr.claim_bond_v2_rewards(&bond_recipient, &pool_owner.pubkey(), Some(unlock_date))
            .await
            .unwrap();
        // Unlocking should be possible now
        tr.unlock_bond_v2_tokens(&bond_recipient, &pool_owner.pubkey(), Some(unlock_date))
            .await
            .unwrap();
        // Check all the stats
        let creator_stats = tr.staker_stats(bond_creator.pubkey()).await.unwrap();
        assert_eq!(
            creator_stats.balance,
            (100_000 - (bond_amount + add_amount)) - ((bond_amount + add_amount) as f64 * tr.get_protocol_fees().await / 100.0).ceil() as u64
        );
        let recipient_stats = tr.staker_stats(bond_recipient.pubkey()).await.unwrap();
        assert_eq!(
            recipient_stats.balance,
            2 * 500_000 + bond_amount + add_amount
        );
        let pool_stats = tr.pool_stats(pool_owner.pubkey()).await.unwrap();
        assert_eq!(pool_stats.header.total_staked, 0);
        assert_eq!(pool_stats.vault, 0);
        let central_state_stats = tr.central_state_stats().await.unwrap();
        assert_eq!(central_state_stats.account.total_staked, 0);
        let bond = tr
            .bond_v2_stats(
                bond_recipient.pubkey(),
                pool_owner.pubkey(),
                Some(unlock_date),
            )
            .await
            .unwrap();
        assert_eq!(bond.tag, BondV2Account);
        assert_eq!(bond.unlock_timestamp, Some(unlock_date));
        assert_eq!(bond.pool, tr.get_pool_pda(&pool_owner.pubkey()));
        assert_eq!(bond.amount, 0);
        assert_eq!(bond.owner, bond_recipient.pubkey());
        assert_eq!(bond.last_claimed_offset, 6);
        assert_eq!(bond.pool_minimum_at_creation, 10_000);

        // Second unlock should not be possible
        _ = tr.sleep(1).await;
        tr.unlock_bond_v2_tokens(&bond_recipient, &pool_owner.pubkey(), Some(unlock_date))
            .await
            .unwrap_err();
    }

    // ---------------------------------------------------------------------------------------------
    // Forever bond
    // ---------------------------------------------------------------------------------------------
    {
        // Create users
        let pool_owner = tr.create_user_with_ata().await.unwrap();
        let bond_creator = tr.create_user_with_ata().await.unwrap();
        let bond_recipient = tr.create_user_with_ata().await.unwrap();
        // Mint to staker
        tr.mint(&bond_creator.pubkey(), 100_000).await.unwrap();
        // Create stake pool
        tr.create_pool(&pool_owner, 10_000)
            .await
            .unwrap();
        // Activate stake pool
        tr.activate_stake_pool(&pool_owner.pubkey()).await.unwrap();

        let token_stats = tr.token_stats().await.unwrap();
        let prev_supply = token_stats.supply;

        // Create bond
        let bond_amount = 30_000;
        tr.create_bond_v2(
            &bond_recipient.pubkey(),
            &pool_owner.pubkey(),
            None,
        )
            .await
            .unwrap();

        // add to bond
        tr.add_to_bond_v2(
            &bond_creator,
            &bond_recipient.pubkey(),
            &pool_owner.pubkey(),
            bond_amount,
            None,
        )
            .await
            .unwrap();

        let staker_stats = tr.staker_stats(bond_creator.pubkey()).await.unwrap();
        assert_eq!(
            staker_stats.balance,
            100_000 - bond_amount // no protocol fee for forever bonds
        );
        let pool_stats = tr.pool_stats(pool_owner.pubkey()).await.unwrap();
        assert_eq!(pool_stats.header.total_staked, bond_amount);
        assert_eq!(pool_stats.vault, 0); // it got burned so it didn't get to the vault
        let central_state_stats = tr.central_state_stats().await.unwrap();
        assert_eq!(central_state_stats.account.total_staked, bond_amount);
        let token_stats = tr.token_stats().await.unwrap();
        assert_eq!(token_stats.supply, prev_supply - bond_amount);
        let bond = tr
            .bond_v2_stats(bond_recipient.pubkey(), pool_owner.pubkey(), None)
            .await
            .unwrap();
        assert_eq!(bond.tag, BondV2Account);
        assert_eq!(bond.unlock_timestamp, None);
        assert_eq!(bond.pool, tr.get_pool_pda(&pool_owner.pubkey()));
        assert_eq!(bond.amount, bond_amount);
        assert_eq!(bond.owner, bond_recipient.pubkey());
        assert_eq!(bond.last_claimed_offset, 6);
        assert_eq!(bond.pool_minimum_at_creation, 10_000);

        // Add to bond
        let add_amount = 40_000;
        tr.add_to_bond_v2(
            &bond_creator,
            &bond_recipient.pubkey(),
            &pool_owner.pubkey(),
            add_amount,
            None,
        )
            .await
            .unwrap();

        let staker_stats = tr.staker_stats(bond_creator.pubkey()).await.unwrap();
        assert_eq!(
            staker_stats.balance,
            100_000 - (bond_amount + add_amount) // no protocol fee for forever bonds
        );
        let pool_stats = tr.pool_stats(pool_owner.pubkey()).await.unwrap();
        assert_eq!(pool_stats.header.total_staked, (bond_amount + add_amount));
        assert_eq!(pool_stats.vault, 0);
        let central_state_stats = tr.central_state_stats().await.unwrap();
        assert_eq!(central_state_stats.account.total_staked, bond_amount + add_amount);
        let bond = tr
            .bond_v2_stats(bond_recipient.pubkey(), pool_owner.pubkey(), None)
            .await
            .unwrap();
        assert_eq!(bond.tag, BondV2Account);
        assert_eq!(bond.unlock_timestamp, None);
        assert_eq!(bond.pool, tr.get_pool_pda(&pool_owner.pubkey()));
        assert_eq!(bond.amount, (bond_amount + add_amount));
        assert_eq!(bond.owner, bond_recipient.pubkey());
        assert_eq!(bond.last_claimed_offset, 6);
        assert_eq!(bond.pool_minimum_at_creation, 10_000);

        // Try unlocking - shouldn't be possible
        tr.unlock_bond_v2_tokens(&bond_recipient, &pool_owner.pubkey(), None)
            .await
            .unwrap_err();
    }
}