Skip to content

Commit

Permalink
Add favorites and escrow projects from professional-education repo
Browse files Browse the repository at this point in the history
Minor biome fixes

Move into proper dirs
  • Loading branch information
mikemaccana committed Jun 13, 2024
1 parent 2c4cb8f commit a6e2528
Show file tree
Hide file tree
Showing 32 changed files with 913 additions and 0 deletions.
9 changes: 9 additions & 0 deletions basics/favorites/anchor/favorites/anchor/favorites/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@

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

.anchor
.DS_Store
target
node_modules
dist
build
test-ledger
18 changes: 18 additions & 0 deletions basics/favorites/anchor/favorites/anchor/favorites/Anchor.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[toolchain]

[features]
resolution = true
skip-lint = false

[programs.localnet]
favorites = "ww9C83noARSQVBnqmCUmaVdbJjmiwcV9j2LkXYMoUCV"

[registry]
url = "https://api.apr.dev"

[provider]
cluster = "Localnet"
wallet = "~/.config/solana/id.json"

[scripts]
test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"
15 changes: 15 additions & 0 deletions basics/favorites/anchor/favorites/anchor/favorites/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[workspace]
members = [
"programs/*"
]
resolver = "2"

[profile.release]
overflow-checks = true
lto = "fat"
codegen-units = 1

[profile.release.build-override]
opt-level = 3
incremental = false
codegen-units = 1
9 changes: 9 additions & 0 deletions basics/favorites/anchor/favorites/anchor/favorites/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Favorites

This is a basic Anchor app using PDAs to store data for a user, and Anchor's account checks to ensure each user is only allowed to modify their own data.

It's used by the [https://github.com/solana-developers/professional-education](Solana Professional Education) course.

## Usage

`anchor test`, `anchor deploy` etc.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Migrations are an early feature. Currently, they're nothing more than this
// single deploy script that's invoked from the CLI, injecting a provider
// configured from the workspace's Anchor.toml.

const anchor = require('@coral-xyz/anchor');

module.exports = async (provider) => {
// Configure client to use the provider.
anchor.setProvider(provider);

// Add your deploy script here.
};
21 changes: 21 additions & 0 deletions basics/favorites/anchor/favorites/anchor/favorites/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"scripts": {
"lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w",
"lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check"
},
"dependencies": {
"@coral-xyz/anchor": "^0.30.0",
"@solana-developers/helpers": "^2.0.0"
},
"license": "UNLICENSED",
"devDependencies": {
"@types/bn.js": "^5.1.0",
"@types/chai": "^4.3.0",
"@types/mocha": "^9.0.0",
"chai": "^4.3.4",
"mocha": "^9.0.3",
"prettier": "^2.6.2",
"ts-mocha": "^10.0.0",
"typescript": "^4.3.5"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[package]
name = "favorites"
version = "0.1.0"
description = "Created with Anchor"
edition = "2021"

[lib]
crate-type = ["cdylib", "lib"]
name = "favorites"

[features]
no-entrypoint = []
no-idl = []
no-log-ix-name = []
cpi = ["no-entrypoint"]
default = []
idl-build = ["anchor-lang/idl-build"]

[dependencies]
anchor-lang = {version = "0.30.0", features = ["init-if-needed"]}
solana-program = "=1.18.5"
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[target.bpfel-unknown-unknown.dependencies.std]
features = []
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
use anchor_lang::prelude::*;
// Our program's address!
// This matches the key in the target/deploy directory
declare_id!("ww9C83noARSQVBnqmCUmaVdbJjmiwcV9j2LkXYMoUCV");

// Anchor programs always use 8 bits for the discriminator
pub const ANCHOR_DISCRIMINATOR_SIZE: usize = 8;

// Our Solana program!
#[program]
pub mod favorites {
use super::*;

// 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);
msg!(
"User {user_public_key}'s favorite number is {number}, favorite color is: {color}",
);

msg!(
"User's hobbies are: {:?}",
hobbies
);

context.accounts.favorites.set_inner(Favorites {
number,
color,
hobbies
});
Ok(())
}

// We can also add a get_favorites instruction handler to return the user's favorite number and color
}

// What we will put inside the Favorites PDA
#[account]
#[derive(InitSpace)]
pub struct Favorites {
pub number: u64,

#[max_len(50)]
pub color: String,

#[max_len(5, 50)]
pub hobbies: Vec<String>
}
// When people call the set_favorites instruction, they will need to provide the accounts that will be modifed. This keeps Solana fast!
#[derive(Accounts)]
pub struct SetFavorites<'info> {
#[account(mut)]
pub user: Signer<'info>,

#[account(
init_if_needed,
payer = user,
space = ANCHOR_DISCRIMINATOR_SIZE + Favorites::INIT_SPACE,
seeds=[b"favorites", user.key().as_ref()],
bump)]
pub favorites: Account<'info, Favorites>,

pub system_program: Program<'info, System>,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import * as anchor from '@coral-xyz/anchor';
import type { Program } from '@coral-xyz/anchor';
import { getCustomErrorMessage } from '@solana-developers/helpers';
import { assert } from 'chai';
import type { Favorites } from '../target/types/favorites';
import { systemProgramErrors } from './system-errors';
const web3 = anchor.web3;

describe('Favorites', () => {
// Use the cluster and the keypair from Anchor.toml
const provider = anchor.AnchorProvider.env();
anchor.setProvider(provider);
const user = (provider.wallet as anchor.Wallet).payer;
const someRandomGuy = anchor.web3.Keypair.generate();
const program = anchor.workspace.Favorites as Program<Favorites>;

// Here's what we want to write to the blockchain
const favoriteNumber = new anchor.BN(23);
const favoriteColor = 'purple';
const favoriteHobbies = ['skiing', 'skydiving', 'biking'];

// We don't need to airdrop if we're using the local cluster
// because the local cluster gives us 85 billion dollars worth of SOL
before(async () => {
const balance = await provider.connection.getBalance(user.publicKey);
const balanceInSOL = balance / web3.LAMPORTS_PER_SOL;
const formattedBalance = new Intl.NumberFormat().format(balanceInSOL);
console.log(`Balance: ${formattedBalance} SOL`);
});

it('Writes our favorites to the blockchain', async () => {
await program.methods
// set_favourites in Rust becomes setFavorites in TypeScript
.setFavorites(favoriteNumber, favoriteColor, favoriteHobbies)
// Sign the transaction
.signers([user])
// Send the transaction to the cluster or RPC
.rpc();

// Find the PDA for the user's favorites
const favoritesPdaAndBump = web3.PublicKey.findProgramAddressSync([Buffer.from('favorites'), user.publicKey.toBuffer()], program.programId);
const favoritesPda = favoritesPdaAndBump[0];
const dataFromPda = await program.account.favorites.fetch(favoritesPda);
// And make sure it matches!
assert.equal(dataFromPda.color, favoriteColor);
// A little extra work to make sure the BNs are equal
assert.equal(dataFromPda.number.toString(), favoriteNumber.toString());
// And check the hobbies too
assert.deepEqual(dataFromPda.hobbies, favoriteHobbies);
});

it('Updates the favorites', async () => {
const newFavoriteHobbies = ['skiing', 'skydiving', 'biking', 'swimming'];
try {
await program.methods.setFavorites(favoriteNumber, favoriteColor, newFavoriteHobbies).signers([user]).rpc();
} catch (error) {
console.error((error as Error).message);
const customErrorMessage = getCustomErrorMessage(systemProgramErrors, error);
throw new Error(customErrorMessage);
}
});

it('Rejects transactions from unauthorized signers', async () => {
try {
await program.methods
// set_favourites in Rust becomes setFavorites in TypeScript
.setFavorites(favoriteNumber, favoriteColor, favoriteHobbies)
// Sign the transaction
.signers([someRandomGuy])
// Send the transaction to the cluster or RPC
.rpc();
} catch (error) {
const errorMessage = (error as Error).message;
assert.isTrue(errorMessage.includes('unknown signer'));
}
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// From https://github.com/solana-labs/solana/blob/a94920a4eadf1008fc292e47e041c1b3b0d949df/sdk/program/src/system_instruction.rs
export const systemProgramErrors = [
'an account with the same address already exists',

'account does not have enough SOL to perform the operation',

'cannot assign account to this program id',

'cannot allocate account data of this length',

'length of requested seed is too long',

'provided address does not match addressed derived from seed',

'advancing stored nonce requires a populated RecentBlockhashes sysvar',

'stored nonce is still in recent_blockhashes',

'specified nonce does not match stored nonce',
];
10 changes: 10 additions & 0 deletions basics/favorites/anchor/favorites/anchor/favorites/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"compilerOptions": {
"types": ["mocha", "chai"],
"typeRoots": ["./node_modules/@types"],
"lib": ["es2015"],
"module": "commonjs",
"target": "es6",
"esModuleInterop": true
}
}
7 changes: 7 additions & 0 deletions tokens/escrow/anchor/escrow/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.anchor
.DS_Store
target
**/*.rs.bk
node_modules
test-ledger
.yarn
7 changes: 7 additions & 0 deletions tokens/escrow/anchor/escrow/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.anchor
.DS_Store
target
node_modules
dist
build
test-ledger
18 changes: 18 additions & 0 deletions tokens/escrow/anchor/escrow/Anchor.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[toolchain]

[features]
resolution = true
skip-lint = false

[programs.localnet]
escrow = "qbuMdeYxYJXBjU6C6qFKjZKjXmrU83eDQomHdrch826"

[registry]
url = "https://api.apr.dev"

[provider]
cluster = "Localnet"
wallet = "~/.config/solana/id.json"

[scripts]
test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"
15 changes: 15 additions & 0 deletions tokens/escrow/anchor/escrow/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[workspace]
members = [
"programs/*"
]
resolver = "2"

[profile.release]
overflow-checks = true
lto = "fat"
codegen-units = 1

[profile.release.build-override]
opt-level = 3
incremental = false
codegen-units = 1
48 changes: 48 additions & 0 deletions tokens/escrow/anchor/escrow/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Anchor Escrow

## Introduction

This Solana program is called an **_escrow_** - it allows a user to swap a specific amount of one token for a desired amount of another token.

For example, Alice is offering 10 USDC, and wants 100 WIF in return.

Without our program, users would have to engage in manual token swapping. Imagine the potential problems if Bob promised to send Alice 100 WIF, but instead took the 10 USDC and ran? Or what if Alice was dishonest, received the 10 USDC from Bob, and decided not to send the 100 WIF? Our Escrow program handles these complexities by acting a trusted entity that will only release tokens to both parties at the right time.

Our Escrow program is designed to provide a secure environment for users to swap a specific amount of one token with a specific amount of another token without having to trust each other.

Better yet, since our program allows Alice and Bob to transact directly with each other, they both get a hundred percent of the token they desire!

## Usage

`anchor test`, `anchor deploy` etc.

## Credit

This project is based on [Dean Little's Anchor Escrow,](https://github.com/deanmlittle/anchor-escrow-2024) with a few changes to make discussion in class easier.

### Changes from original

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.
- 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:

- Renamed deposit #1 -> 'token_a_offered_amount'
- Renamed deposit #2 (in make() ) -> 'send_offered_tokens_to_vault'
- Renamed deposit #3 (in take() ) -> 'send_wanted_tokens_to_maker'

- 'seed' was renamed to 'id' because 'seed' as it conflicted with the 'seeds' used for PDA address generation.
- 'Escrow' was used for the program's name and the account that records details of the offer. This wasn't great because people would confuse 'Escrow' with the 'Vault'.

- Escrow (the program) -> remains Escrow
- Escrow (the offer) -> Offer.

- 'receive' was renamed to 'token_b_wanted_amount' as 'receive' is a verb and not a suitable name for an integer.
- mint_a -> token_mint_a (ie, what the maker has offered and what the taker wants)
- mint_b -> token_mint_b (ie, what that maker wants and what the taker must offer)
- makerAtaA -> makerTokenAccountA,
- makerAtaB -> makerTokenAccountB
- takerAtaA -> takerTokenAccountA
- takerAtaB -> takerTokenAccountB
Loading

0 comments on commit a6e2528

Please sign in to comment.