Skip to content

Commit

Permalink
Merge branch 'main' into anchor-bankrun
Browse files Browse the repository at this point in the history
  • Loading branch information
heyAyushh authored Aug 20, 2024
2 parents ed83415 + 8139019 commit 9973279
Show file tree
Hide file tree
Showing 23 changed files with 2,303 additions and 1,774 deletions.
5 changes: 3 additions & 2 deletions basics/favorites/anchor/programs/favorites/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ pub mod favorites {

// Our instruction handler! It sets the user's favorite number and color
pub fn set_favorites(context: Context<SetFavorites>, number: u64, color: String, hobbies: Vec<String>) -> Result<()> {
let user_public_key = context.accounts.user.key();
msg!("Greetings from {}", context.program_id);
let user_public_key = context.accounts.user.key();
msg!(
"User {user_public_key}'s favorite number is {number}, favorite color is: {color}, and their hobbies are {hobbies:?}",
);
Expand Down Expand Up @@ -53,7 +53,8 @@ pub struct SetFavorites<'info> {
payer = user,
space = ANCHOR_DISCRIMINATOR_SIZE + Favorites::INIT_SPACE,
seeds=[b"favorites", user.key().as_ref()],
bump)]
bump
)]
pub favorites: Account<'info, Favorites>,

pub system_program: Program<'info, System>,
Expand Down
4 changes: 3 additions & 1 deletion basics/favorites/anchor/tests/favorites.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ describe('Favorites', () => {
it('Updates the favorites', async () => {
const newFavoriteHobbies = ['skiing', 'skydiving', 'biking', 'swimming'];
try {
await program.methods.setFavorites(favoriteNumber, favoriteColor, newFavoriteHobbies).signers([user]).rpc();
const signature = await program.methods.setFavorites(favoriteNumber, favoriteColor, newFavoriteHobbies).signers([user]).rpc();

console.log(`Transaction signature: ${signature}`);
} catch (error) {
console.error((error as Error).message);
const customErrorMessage = getCustomErrorMessage(systemProgramErrors, error);
Expand Down
1 change: 1 addition & 0 deletions tokens/escrow/anchor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ This project is based on [Dean Little's Anchor Escrow,](https://github.com/deanm
One of the challenges when teaching is avoiding ambiguity — names have to be carefully chosen to be clear and not possible to confuse with other times.

- Custom instructions were replaced by `@solana-developers/helpers` for many tasks to reduce the file size.
- Shared functionality to transfer tokens is now in `instructions/shared.rs`
- The upstream project has a custom file layout. We use the 'multiple files' Anchor layout.
- Contexts are separate data structures from functions that use the contexts. There is no need for OO-like `impl` patterns here - there's no mutable state stored in the Context, and the 'methods' do not mutate that state. Besides, it's easier to type!
- The name 'deposit' was being used in multiple contexts, and `deposit` can be tough because it's a verb and a noun:
Expand Down
1,661 changes: 0 additions & 1,661 deletions tokens/escrow/anchor/pnpm-lock.yaml

Large diffs are not rendered by default.

27 changes: 10 additions & 17 deletions tokens/escrow/anchor/programs/escrow/src/instructions/make_offer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ use anchor_lang::prelude::*;

use anchor_spl::{
associated_token::AssociatedToken,
token_interface::{transfer_checked, Mint, TokenAccount, TokenInterface, TransferChecked},
token_interface::{Mint, TokenAccount, TokenInterface},
};

use crate::{Offer, ANCHOR_DISCRIMINATOR};

use super::transfer_tokens;

// See https://www.anchor-lang.com/docs/account-constraints#instruction-attribute
#[derive(Accounts)]
#[instruction(id: u64)]
Expand Down Expand Up @@ -56,22 +58,13 @@ pub fn send_offered_tokens_to_vault(
context: &Context<MakeOffer>,
token_a_offered_amount: u64,
) -> Result<()> {
let transfer_accounts = TransferChecked {
from: context.accounts.maker_token_account_a.to_account_info(),
mint: context.accounts.token_mint_a.to_account_info(),
to: context.accounts.vault.to_account_info(),
authority: context.accounts.maker.to_account_info(),
};

let cpi_context = CpiContext::new(
context.accounts.token_program.to_account_info(),
transfer_accounts,
);

transfer_checked(
cpi_context,
token_a_offered_amount,
context.accounts.token_mint_a.decimals,
transfer_tokens(
&context.accounts.maker_token_account_a,
&context.accounts.vault,
&token_a_offered_amount,
&context.accounts.token_mint_a,
&context.accounts.maker,
&context.accounts.token_program,
)
}

Expand Down
3 changes: 3 additions & 0 deletions tokens/escrow/anchor/programs/escrow/src/instructions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ pub use make_offer::*;

pub mod take_offer;
pub use take_offer::*;

pub mod shared;
pub use shared::*;
25 changes: 25 additions & 0 deletions tokens/escrow/anchor/programs/escrow/src/instructions/shared.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use anchor_lang::prelude::*;

use anchor_spl::token_interface::{
transfer_checked, Mint, TokenAccount, TokenInterface, TransferChecked,
};

pub fn transfer_tokens<'info>(
from: &InterfaceAccount<'info, TokenAccount>,
to: &InterfaceAccount<'info, TokenAccount>,
amount: &u64,
mint: &InterfaceAccount<'info, Mint>,
authority: &Signer<'info>,
token_program: &Interface<'info, TokenInterface>,
) -> Result<()> {
let transfer_accounts = TransferChecked {
from: from.to_account_info(),
mint: mint.to_account_info(),
to: to.to_account_info(),
authority: authority.to_account_info(),
};

let cpi_context = CpiContext::new(token_program.to_account_info(), transfer_accounts);

transfer_checked(cpi_context, *amount, mint.decimals)
}
30 changes: 12 additions & 18 deletions tokens/escrow/anchor/programs/escrow/src/instructions/take_offer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ use anchor_spl::{

use crate::Offer;

use super::transfer_tokens;

#[derive(Accounts)]
pub struct TakeOffer<'info> {
#[account(mut)]
Expand Down Expand Up @@ -73,32 +75,24 @@ pub struct TakeOffer<'info> {
}

pub fn send_wanted_tokens_to_maker(ctx: &Context<TakeOffer>) -> Result<()> {
let transfer_accounts = TransferChecked {
from: ctx.accounts.taker_token_account_b.to_account_info(),
mint: ctx.accounts.token_mint_b.to_account_info(),
to: ctx.accounts.maker_token_account_b.to_account_info(),
authority: ctx.accounts.taker.to_account_info(),
};

let cpi_ctx = CpiContext::new(
ctx.accounts.token_program.to_account_info(),
transfer_accounts,
);

transfer_checked(
cpi_ctx,
ctx.accounts.offer.token_b_wanted_amount,
ctx.accounts.token_mint_b.decimals,
transfer_tokens(
&ctx.accounts.taker_token_account_b,
&ctx.accounts.maker_token_account_b,
&ctx.accounts.offer.token_b_wanted_amount,
&ctx.accounts.token_mint_b,
&ctx.accounts.taker,
&ctx.accounts.token_program,
)
}

pub fn withdraw_and_close_vault(ctx: Context<TakeOffer>) -> Result<()> {
let signer_seeds: [&[&[u8]]; 1] = [&[
let seeds = &[
b"offer",
ctx.accounts.maker.to_account_info().key.as_ref(),
&ctx.accounts.offer.id.to_le_bytes()[..],
&[ctx.accounts.offer.bump],
]];
];
let signer_seeds = [&seeds[..]];

let accounts = TransferChecked {
from: ctx.accounts.vault.to_account_info(),
Expand Down
145 changes: 70 additions & 75 deletions tokens/escrow/anchor/tests/escrow.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,35 @@
import { randomBytes } from 'node:crypto';
import * as anchor from '@coral-xyz/anchor';
import { BN, type Program } from '@coral-xyz/anchor';
import {
MINT_SIZE,
TOKEN_2022_PROGRAM_ID,
type TOKEN_PROGRAM_ID,
createAssociatedTokenAccountIdempotentInstruction,
createInitializeMint2Instruction,
createMintToInstruction,
getAssociatedTokenAddressSync,
getMinimumBalanceForRentExemptMint,
} from '@solana/spl-token';
import { LAMPORTS_PER_SOL, PublicKey, SystemProgram, Transaction, type TransactionInstruction } from '@solana/web3.js';
import { TOKEN_2022_PROGRAM_ID, type TOKEN_PROGRAM_ID, getAssociatedTokenAddressSync } from '@solana/spl-token';
import { LAMPORTS_PER_SOL, PublicKey } from '@solana/web3.js';
import { assert } from 'chai';
import type { Escrow } from '../target/types/escrow';

import { confirmTransaction, makeKeypairs } from '@solana-developers/helpers';
import { confirmTransaction, createAccountsMintsAndTokenAccounts, makeKeypairs } from '@solana-developers/helpers';

// Work on both Token Program and new Token Extensions Program
const TOKEN_PROGRAM: typeof TOKEN_2022_PROGRAM_ID | typeof TOKEN_PROGRAM_ID = TOKEN_2022_PROGRAM_ID;

const SECONDS = 1000;

// Tests must complete within half this time otherwise
// they are marked as slow. Since Anchor involves a little
// network IO, these tests usually take about 15 seconds.
const ANCHOR_SLOW_TEST_THRESHOLD = 40 * SECONDS;

const getRandomBigNumber = (size = 8) => {
return new BN(randomBytes(size));
};

describe('escrow', async () => {
anchor.setProvider(anchor.AnchorProvider.env());
// Use the cluster and the keypair from Anchor.toml
const provider = anchor.AnchorProvider.env();
anchor.setProvider(provider);

const provider = anchor.getProvider();
// See https://github.com/coral-xyz/anchor/issues/3122
const user = (provider.wallet as anchor.Wallet).payer;
const payer = user;

const connection = provider.connection;

Expand All @@ -37,57 +40,62 @@ describe('escrow', async () => {
tokenProgram: TOKEN_PROGRAM,
};

const [alice, bob, tokenMintA, tokenMintB] = makeKeypairs(4);
let alice: anchor.web3.Keypair;
let bob: anchor.web3.Keypair;
let tokenMintA: anchor.web3.Keypair;
let tokenMintB: anchor.web3.Keypair;

[alice, bob, tokenMintA, tokenMintB] = makeKeypairs(4);

const tokenAOfferedAmount = new BN(1_000_000);
const tokenBWantedAmount = new BN(1_000_000);

before('Creates Alice and Bob accounts, 2 token mints, and associated token accounts for both tokens for both users', async () => {
const [aliceTokenAccountA, aliceTokenAccountB, bobTokenAccountA, bobTokenAccountB] = [alice, bob].flatMap((keypair) =>
[tokenMintA, tokenMintB].map((mint) => getAssociatedTokenAddressSync(mint.publicKey, keypair.publicKey, false, TOKEN_PROGRAM)),
const usersMintsAndTokenAccounts = await createAccountsMintsAndTokenAccounts(
[
// Alice's token balances
[
// 1_000_000_000 of token A
1_000_000_000,
// 0 of token B
0,
],
// Bob's token balances
[
// 0 of token A
0,
// 1_000_000_000 of token B
1_000_000_000,
],
],
1 * LAMPORTS_PER_SOL,
connection,
payer,
);

// Airdrops to users, and creates two tokens mints 'A' and 'B'"
const minimumLamports = await getMinimumBalanceForRentExemptMint(connection);
// Alice will be the maker (creator) of the offer
// Bob will be the taker (acceptor) of the offer
const users = usersMintsAndTokenAccounts.users;
alice = users[0];
bob = users[1];

const sendSolInstructions: Array<TransactionInstruction> = [alice, bob].map((account) =>
SystemProgram.transfer({
fromPubkey: provider.publicKey,
toPubkey: account.publicKey,
lamports: 10 * LAMPORTS_PER_SOL,
}),
);
// tokenMintA represents the token Alice is offering
// tokenMintB represents the token Alice wants in return
const mints = usersMintsAndTokenAccounts.mints;
tokenMintA = mints[0];
tokenMintB = mints[1];

const createMintInstructions: Array<TransactionInstruction> = [tokenMintA, tokenMintB].map((mint) =>
SystemProgram.createAccount({
fromPubkey: provider.publicKey,
newAccountPubkey: mint.publicKey,
lamports: minimumLamports,
space: MINT_SIZE,
programId: TOKEN_PROGRAM,
}),
);
const tokenAccounts = usersMintsAndTokenAccounts.tokenAccounts;

// aliceTokenAccountA is Alice's account for tokenA (the token she's offering)
// aliceTokenAccountB is Alice's account for tokenB (the token she wants)
const aliceTokenAccountA = tokenAccounts[0][0];
const aliceTokenAccountB = tokenAccounts[0][1];

// Make tokenA and tokenB mints, mint tokens and create ATAs
const mintTokensInstructions: Array<TransactionInstruction> = [
{
mint: tokenMintA.publicKey,
authority: alice.publicKey,
ata: aliceTokenAccountA,
},
{
mint: tokenMintB.publicKey,
authority: bob.publicKey,
ata: bobTokenAccountB,
},
].flatMap((mintDetails) => [
createInitializeMint2Instruction(mintDetails.mint, 6, mintDetails.authority, null, TOKEN_PROGRAM),
createAssociatedTokenAccountIdempotentInstruction(provider.publicKey, mintDetails.ata, mintDetails.authority, mintDetails.mint, TOKEN_PROGRAM),
createMintToInstruction(mintDetails.mint, mintDetails.ata, mintDetails.authority, 1_000_000_000, [], TOKEN_PROGRAM),
]);

// Add all these instructions to our transaction
const tx = new Transaction();
tx.instructions = [...sendSolInstructions, ...createMintInstructions, ...mintTokensInstructions];

await provider.sendAndConfirm(tx, [tokenMintA, tokenMintB, alice, bob]);
// bobTokenAccountA is Bob's account for tokenA (the token Alice is offering)
// bobTokenAccountB is Bob's account for tokenB (the token Alice wants)
const bobTokenAccountA = tokenAccounts[1][0];
const bobTokenAccountB = tokenAccounts[1][1];

// Save the accounts for later use
accounts.maker = alice.publicKey;
Expand All @@ -100,11 +108,7 @@ describe('escrow', async () => {
accounts.takerTokenAccountB = bobTokenAccountB;
});

const tokenAOfferedAmount = new BN(1_000_000);
const tokenBWantedAmount = new BN(1_000_000);

// We'll call this function from multiple tests, so let's seperate it out
const make = async () => {
it('Puts the tokens Alice offers into the vault when Alice makes an offer', async () => {
// Pick a random ID for the offer we'll make
const offerId = getRandomBigNumber();

Expand Down Expand Up @@ -139,10 +143,9 @@ describe('escrow', async () => {
assert(offerAccount.tokenMintA.equals(accounts.tokenMintA));
assert(offerAccount.tokenMintB.equals(accounts.tokenMintB));
assert(offerAccount.tokenBWantedAmount.eq(tokenBWantedAmount));
};
}).slow(ANCHOR_SLOW_TEST_THRESHOLD);

// We'll call this function from multiple tests, so let's seperate it out
const take = async () => {
it("Puts the tokens from the vault into Bob's account, and gives Alice Bob's tokens, when Bob takes an offer", async () => {
const transactionSignature = await program.methods
.takeOffer()
.accounts({ ...accounts })
Expand All @@ -162,13 +165,5 @@ describe('escrow', async () => {
const aliceTokenAccountBalanceAfterResponse = await connection.getTokenAccountBalance(accounts.makerTokenAccountB);
const aliceTokenAccountBalanceAfter = new BN(aliceTokenAccountBalanceAfterResponse.value.amount);
assert(aliceTokenAccountBalanceAfter.eq(tokenBWantedAmount));
};

it('Puts the tokens Alice offers into the vault when Alice makes an offer', async () => {
await make();
});

it("Puts the tokens from the vault into Bob's account, and gives Alice Bob's tokens, when Bob takes an offer", async () => {
await take();
});
}).slow(ANCHOR_SLOW_TEST_THRESHOLD);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

.anchor
.DS_Store
target
**/*.rs.bk
node_modules
test-ledger
.yarn
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

.anchor
.DS_Store
target
node_modules
dist
build
test-ledger
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"tabWidth": 2,
"useTabs": false,
"singleQuote": false,
"printWidth": 80,
"trailingComma": "all",
"arrowParens": "avoid",
"endOfLine": "auto",
"proseWrap": "always"
}
Loading

0 comments on commit 9973279

Please sign in to comment.