From 92f5a9eda0e10a630f07ab1dd639268207548384 Mon Sep 17 00:00:00 2001 From: hyckomatej Date: Wed, 20 Nov 2024 09:41:18 +0100 Subject: [PATCH] Update lesson order --- 6.lesson/README.md | 407 +---------------- {7.lesson => 6.lesson}/bank/.gitignore | 0 {7.lesson => 6.lesson}/bank/.prettierignore | 0 {7.lesson => 6.lesson}/bank/Anchor.toml | 0 {7.lesson => 6.lesson}/bank/Cargo.lock | 0 {7.lesson => 6.lesson}/bank/Cargo.toml | 0 .../bank/migrations/deploy.ts | 0 {7.lesson => 6.lesson}/bank/package.json | 0 {7.lesson => 6.lesson}/bank/payer.json | 0 .../bank/programs/bank/Cargo.toml | 0 .../bank/programs/bank/Xargo.toml | 0 .../bank/programs/bank/src/lib.rs | 0 {7.lesson => 6.lesson}/bank/tests/bank.ts | 0 {7.lesson => 6.lesson}/bank/tsconfig.json | 0 {7.lesson => 6.lesson}/bank/yarn.lock | 0 {7.lesson => 6.lesson}/frontend/Dockerfile | 0 {7.lesson => 6.lesson}/frontend/LICENSE | 0 {7.lesson => 6.lesson}/frontend/README.md | 0 {7.lesson => 6.lesson}/frontend/next-env.d.ts | 0 .../frontend/next.config.js | 0 .../frontend/package-lock.json | 0 {7.lesson => 6.lesson}/frontend/package.json | 0 .../frontend/postcss.config.js | 0 .../frontend/public/favicon.ico | Bin .../frontend/public/solanaLogo.png | Bin .../frontend/public/vercel.svg | 0 .../frontend/scaffold-desktop.png | Bin .../frontend/scaffold-mobile.png | Bin .../frontend/src/components/AppBar.tsx | 0 .../frontend/src/components/Bank.tsx | 0 .../src/components/ContentContainer.tsx | 0 .../frontend/src/components/Footer.tsx | 0 .../src/components/NetworkSwitcher.tsx | 0 .../frontend/src/components/Notification.tsx | 0 .../src/components/RequestAirdrop.tsx | 0 .../src/components/SendTransaction.tsx | 0 .../components/SendVersionedTransaction.tsx | 0 .../frontend/src/components/SignMessage.tsx | 0 .../frontend/src/components/Text/index.tsx | 0 .../frontend/src/components/bank.json | 0 .../frontend/src/components/bank.ts | 0 .../src/components/nav-element/index.tsx | 0 .../src/contexts/AutoConnectProvider.tsx | 0 .../frontend/src/contexts/ContextProvider.tsx | 0 .../contexts/NetworkConfigurationProvider.tsx | 0 .../frontend/src/hooks/useQueryContext.tsx | 0 .../frontend/src/models/types.ts | 0 .../frontend/src/pages/_app.tsx | 0 .../frontend/src/pages/_document.tsx | 0 .../frontend/src/pages/api/hello.ts | 0 .../frontend/src/pages/basics.tsx | 0 .../frontend/src/pages/index.tsx | 0 .../src/stores/useNotificationStore.tsx | 0 .../src/stores/useUserSOLBalanceStore.tsx | 0 .../frontend/src/styles/globals.css | 0 .../frontend/src/utils/explorer.ts | 0 .../frontend/src/utils/index.tsx | 0 .../frontend/src/utils/notifications.tsx | 0 .../frontend/src/views/basics/index.tsx | 0 .../frontend/src/views/home/index.tsx | 0 .../frontend/src/views/index.tsx | 0 .../frontend/tailwind.config.js | 0 {7.lesson => 6.lesson}/frontend/tsconfig.json | 0 {7.lesson => 6.lesson}/frontend/yarn.lock | 0 {7.lesson => 6.lesson}/solana_banks.png | Bin 7.lesson/README.md | 428 +++++++++++++++++- 66 files changed, 428 insertions(+), 407 deletions(-) rename {7.lesson => 6.lesson}/bank/.gitignore (100%) rename {7.lesson => 6.lesson}/bank/.prettierignore (100%) rename {7.lesson => 6.lesson}/bank/Anchor.toml (100%) rename {7.lesson => 6.lesson}/bank/Cargo.lock (100%) rename {7.lesson => 6.lesson}/bank/Cargo.toml (100%) rename {7.lesson => 6.lesson}/bank/migrations/deploy.ts (100%) rename {7.lesson => 6.lesson}/bank/package.json (100%) rename {7.lesson => 6.lesson}/bank/payer.json (100%) rename {7.lesson => 6.lesson}/bank/programs/bank/Cargo.toml (100%) rename {7.lesson => 6.lesson}/bank/programs/bank/Xargo.toml (100%) rename {7.lesson => 6.lesson}/bank/programs/bank/src/lib.rs (100%) rename {7.lesson => 6.lesson}/bank/tests/bank.ts (100%) rename {7.lesson => 6.lesson}/bank/tsconfig.json (100%) rename {7.lesson => 6.lesson}/bank/yarn.lock (100%) rename {7.lesson => 6.lesson}/frontend/Dockerfile (100%) rename {7.lesson => 6.lesson}/frontend/LICENSE (100%) rename {7.lesson => 6.lesson}/frontend/README.md (100%) rename {7.lesson => 6.lesson}/frontend/next-env.d.ts (100%) rename {7.lesson => 6.lesson}/frontend/next.config.js (100%) rename {7.lesson => 6.lesson}/frontend/package-lock.json (100%) rename {7.lesson => 6.lesson}/frontend/package.json (100%) rename {7.lesson => 6.lesson}/frontend/postcss.config.js (100%) rename {7.lesson => 6.lesson}/frontend/public/favicon.ico (100%) rename {7.lesson => 6.lesson}/frontend/public/solanaLogo.png (100%) rename {7.lesson => 6.lesson}/frontend/public/vercel.svg (100%) rename {7.lesson => 6.lesson}/frontend/scaffold-desktop.png (100%) rename {7.lesson => 6.lesson}/frontend/scaffold-mobile.png (100%) rename {7.lesson => 6.lesson}/frontend/src/components/AppBar.tsx (100%) rename {7.lesson => 6.lesson}/frontend/src/components/Bank.tsx (100%) rename {7.lesson => 6.lesson}/frontend/src/components/ContentContainer.tsx (100%) rename {7.lesson => 6.lesson}/frontend/src/components/Footer.tsx (100%) rename {7.lesson => 6.lesson}/frontend/src/components/NetworkSwitcher.tsx (100%) rename {7.lesson => 6.lesson}/frontend/src/components/Notification.tsx (100%) rename {7.lesson => 6.lesson}/frontend/src/components/RequestAirdrop.tsx (100%) rename {7.lesson => 6.lesson}/frontend/src/components/SendTransaction.tsx (100%) rename {7.lesson => 6.lesson}/frontend/src/components/SendVersionedTransaction.tsx (100%) rename {7.lesson => 6.lesson}/frontend/src/components/SignMessage.tsx (100%) rename {7.lesson => 6.lesson}/frontend/src/components/Text/index.tsx (100%) rename {7.lesson => 6.lesson}/frontend/src/components/bank.json (100%) rename {7.lesson => 6.lesson}/frontend/src/components/bank.ts (100%) rename {7.lesson => 6.lesson}/frontend/src/components/nav-element/index.tsx (100%) rename {7.lesson => 6.lesson}/frontend/src/contexts/AutoConnectProvider.tsx (100%) rename {7.lesson => 6.lesson}/frontend/src/contexts/ContextProvider.tsx (100%) rename {7.lesson => 6.lesson}/frontend/src/contexts/NetworkConfigurationProvider.tsx (100%) rename {7.lesson => 6.lesson}/frontend/src/hooks/useQueryContext.tsx (100%) rename {7.lesson => 6.lesson}/frontend/src/models/types.ts (100%) rename {7.lesson => 6.lesson}/frontend/src/pages/_app.tsx (100%) rename {7.lesson => 6.lesson}/frontend/src/pages/_document.tsx (100%) rename {7.lesson => 6.lesson}/frontend/src/pages/api/hello.ts (100%) rename {7.lesson => 6.lesson}/frontend/src/pages/basics.tsx (100%) rename {7.lesson => 6.lesson}/frontend/src/pages/index.tsx (100%) rename {7.lesson => 6.lesson}/frontend/src/stores/useNotificationStore.tsx (100%) rename {7.lesson => 6.lesson}/frontend/src/stores/useUserSOLBalanceStore.tsx (100%) rename {7.lesson => 6.lesson}/frontend/src/styles/globals.css (100%) rename {7.lesson => 6.lesson}/frontend/src/utils/explorer.ts (100%) rename {7.lesson => 6.lesson}/frontend/src/utils/index.tsx (100%) rename {7.lesson => 6.lesson}/frontend/src/utils/notifications.tsx (100%) rename {7.lesson => 6.lesson}/frontend/src/views/basics/index.tsx (100%) rename {7.lesson => 6.lesson}/frontend/src/views/home/index.tsx (100%) rename {7.lesson => 6.lesson}/frontend/src/views/index.tsx (100%) rename {7.lesson => 6.lesson}/frontend/tailwind.config.js (100%) rename {7.lesson => 6.lesson}/frontend/tsconfig.json (100%) rename {7.lesson => 6.lesson}/frontend/yarn.lock (100%) rename {7.lesson => 6.lesson}/solana_banks.png (100%) diff --git a/6.lesson/README.md b/6.lesson/README.md index 6d65915..1f71126 100644 --- a/6.lesson/README.md +++ b/6.lesson/README.md @@ -1,409 +1,34 @@ -# 6. Lecture - Security +# 6. Lecture - Frontend for Solana Programs -## Table of Contents -- [Common Solana Attack Vectors](#common-solana-attack-vectors) -- [Solana Runtime Policy](#solana-runtime-policy) - - [Immutability](#immutability) - - [Data](#data) - - [Ownership](#ownership) - - [Zero Balance](#zero-balance) - - [Transaction](#transaction) - - [Data Allocation](#data-allocation) - - [Balance](#balance) - - [Rent](#rent) -- [Best Security Practices](#best-security-practices) - - [Signer Authorization](#signer-authorization) - - [Owner Checks](#owner-checks) - - [Account Data Matching](#account-data-matching) - - [Reinitialization Attacks](#reinitialization-attacks) - - [Duplicate Mutable Accounts](#duplicate-mutable-accounts) - - [Type Cosplay](#type-cosplay) - - [Arbitrary CPI](#arbitrary-cpi) - - [Bump Seed Canonicalization](#bump-seed-canonicalization) - - [Closing Accounts and Revival Attacks](#closing-accounts-and-revival-attacks) ---- +This week's lecture focuses on teaching you how to build a simple frontend for your dApp. -# Common Solana Attack Vectors +The repository contains the source code for both the Anchor program as well as the frontend of the bank example created during the lecture. -> [!IMPORTANT] -> For the Common Attack Vectors on Solana check the [Common Attack Vectors](https://github.com/Ackee-Blockchain/Solana-Auditors-Bootcamp/tree/master/Lesson-6) created for Solana Auditors Bootcamp. +**The Anchor program is already deployed**, so you can interact with it by simply starting the frontend and connecting your hot wallet. Make sure your wallet is set to **devnet** mode. -# Solana Runtime Policy + -## Immutability -> Executable accounts are fully immutable. -> Executable is one-way (false->true) and only the account owner may set it. +>[!TIP] +>### Try Deploying the Program Yourself! +>You will need to update the program ID as demonstrated at the start of the lecture. The current program ID is already in use. -## Data +## Setup -> Only the owner of an account may modify its data. +To set up the frontend, navigate to the `frontend` directory and install the dependencies: -> Accounts may only be assigned a new owner if their data is zeroed out. -And only if the account is not executable. And only if the account is writable. - -## Ownership - -> Only the owner of an account may assign a new owner. - -## Zero Balance - -> Accounts with zero balance will be deleted at the end of transaction processing. - -> Temporary accounts with zero balance may be created during a transaction. - -## Transaction - -> Total balances on all the accounts are equal before and after the execution of a transaction. - -> After the transaction is executed, balances of read-only accounts must be equal to the balances before the transaction. - -> All instructions in the transaction are executed atomically. If one fails, all account modifications are discarded. - -## Data Allocation - -> Only the owner may change account size and data. And if the account is writable. And if the account is not executable. - -> Newly allocated account data is always zeroed out. - -## Balance - -> Only the owner of an account may subtract its lamports. - -> Any program account may add lamports to an account. - -## Rent - -> Rent fees are charged every epoch and are determined by account size. - -> Accounts with sufficient balance to cover 2 years of rent are exempt from fees. - - - - - -# Best Security Practices - -## Signer Authorization - -### Plain Rust - -Use `Signer Checks` to verify that specific accounts have signed a transaction. Without appropriate signer checks, accounts may be able to execute instructions they shouldn’t be authorized to perform. - -To implement a signer check in Rust, simply check that an account’s `is_signer` property is `true` - -```rust -if !ctx.accounts.authority.is_signer { - return Err(ProgramError::MissingRequiredSignature.into()); -} -``` - -### Anchor -In Anchor, you can use the `Signer` account type in your account validation struct to have Anchor automatically perform a signer check on a given account - -```rust -#[derive(Accounts)] -pub struct UpdateUserData<'info> { - // performs signer check - pub user: Signer<'info>, -} -``` - -## Owner Checks - -### Plain Rust - -Use `Owner Checks` to verify that accounts are owned by the expected program. Without appropriate owner checks, accounts owned by unexpected programs could be used in an instruction. - -To implement an owner check in Rust, simply check that an account’s owner matches an expected program ID - -```rust -if ctx.accounts.account.owner != ctx.program_id { - return Err(ProgramError::IncorrectProgramId.into()); -} -``` - -### Anchor - -Anchor program account types implement the `Owner` trait which allows the `Account<'info, T>` wrapper to automatically verify program ownership - -```rust -#[derive(Accounts)] -pub struct UpdateUserData<'info> { - pub user: Signer<'info>, - // performs ownership check - pub user_data: Account<'info, UserData>, -} -``` - -Anchor gives you the option to explicitly define the owner of an account if it should be anything other than the currently executing program - -## Account Data Matching - -### Plain Rust - -Use `Data Validation checks` to verify that account data matches an expected value. Without appropriate data validation checks, unexpected accounts may be used in an instruction. - -To implement data validation checks in Rust, simply compare the data stored on an account to an expected value. - -```rust -if ctx.accounts.user.key() != ctx.accounts.user_data.user { - return Err(ProgramError::InvalidAccountData.into()); -} -``` - -### Anchor - -In Anchor, you can use `constraint` to checks whether the given expression evaluates to true. Alternatively, you can use `has_one` to check that a target account field stored on the account matches the key of an account in the `Accounts` struct. - -```rust -#[derive(Accounts)] -pub struct UpdateUserData<'info> { - pub user: Signer<'info>, - #[account(constraint = user_data.authority = user.key())] - pub user_data: Account<'info, UserData>, -} -``` - -## Reinitialization Attacks - -### Plain Rust - -Use an `account discriminator or initialization flag` to check whether an account has already been initialized to prevent an account from being reinitialized and overriding existing account data. - -To prevent account reinitialization in plain Rust, initialize accounts with an `is_initialized` flag and check if it has already been set to true when initializing an account - -```rust -if account.is_initialized { - return Err(ProgramError::AccountAlreadyInitialized.into()); -} -``` - -### Anchor - -To simplify this, use Anchor’s `init` constraint to create an account via a CPI to the system program and sets its discriminator - -```rust -#[derive(Accounts)] -pub struct UpdateUserData<'info> { - pub user: Signer<'info>, - // if account is already initialized init will return error - #[account(init)] - pub user_data: Account<'info, UserData>, -} -``` - -## Duplicate Mutable Accounts - -### Plain Rust - -When an instruction requires two mutable accounts of the same type, an attacker can pass in the same account twice, causing the account to be mutated in unintended ways. - -To check for duplicate mutable accounts in Rust, simply compare the public keys of the two accounts and throw an error if they are the same. - -```rust -if ctx.accounts.account_one.key() == ctx.accounts.account_two.key() { - return Err(ProgramError::InvalidArgument) -} -``` - -### Anchor - -In Anchor, you can use constraint to add an explicit constraint to an account checking that it is not the same as another account. - -```rust -#[derive(Accounts)] -pub struct UpdateUserData<'info> { - pub user: Signer<'info>, - #[account( - mut, - constraint = user_data.key() != different_user_data.key() - )] - pub user_data: Account<'info, UserData>, - #[account(mut)] - pub different_user_data: Account<'info, UserData>, -} +```bash +npm install ``` -## Type Cosplay - -### Plain Rust +Then, start the frontend with the following command: -Use discriminators to distinguish between different account types - -To implement a discriminator in Rust, include a field in the account struct to represent the account type - -```rust -#[derive(BorshSerialize, BorshDeserialize)] -pub struct User { - discriminant: AccountDiscriminant, - user: Pubkey, -} - -#[derive(BorshSerialize, BorshDeserialize, PartialEq)] -pub enum AccountDiscriminant { - User, - Admin, -} -``` - -To implement a discriminator check in Rust, verify that the discriminator of the deserialized account data matches the expected value - -```rust -if user.discriminant != AccountDiscriminant::User { - return Err(ProgramError::InvalidAccountData.into()); -} -``` - -### Anchor - -In Anchor, program account types automatically implement the `Discriminator` trait which creates an 8 byte unique identifier for a type - -Use Anchor’s `Account<'info, T>` type to automatically check the discriminator of the account when deserializing the account data - -```rust -#[derive(Accounts)] -pub struct UpdateUserData<'info> { - pub user: Signer<'info>, - // Account type automatically checks the Discriminator - // of the UserData Account - pub user_data: Account<'info, UserData>, -} -``` - -## Arbitrary CPI - -### Plain Rust - -To generate a CPI, the target program must be passed into the invoking instruction as an account. This means that any target program could be passed into the instruction. Your program should check for incorrect or unexpected programs. - -Perform program checks in native programs by simply comparing the public key of the passed-in program to the program you expected. - -```rust -pub fn cpi_secure(ctx: Context, amount: u64) -> Result<()> { - if &spl_token::ID != ctx.accounts.token_program.key { - return Err(ProgramError::IncorrectProgramId); - } - ... - // CPI logic goes here -} -``` - -### Anchor - -If a program is written in Anchor, then it may have a publicly available CPI module. This makes invoking the program from another Anchor program simple and secure. The Anchor CPI module automatically checks that the address of the program passed in matches the address of the program stored in the module. - -The best practice while using Anchor is to always use `Program<'info, T>`, which will check that the account is executable and it is the given Program. For example: - -```rust -use anchor_spl::token::Token; - -#[derive(Accounts)] -pub struct InitializeExchange<'info> { - // ... - pub token_program: Program<'info, Token>, - // ... -} -``` - -## Bump Seed Canonicalization - -### Plain Rust - -The [`create_program_address`](https://docs.rs/solana-program/latest/solana_program/pubkey/struct.Pubkey.html#method.create_program_address) function derives a PDA without searching for the `canonical bump`. This means there may be multiple valid bumps, all of which will produce different addresses. - -Using [`find_program_address`](https://docs.rs/solana-program/latest/solana_program/pubkey/struct.Pubkey.html#method.find_program_address) ensures that the highest valid bump, or canonical bump, is used for the derivation, thus creating a deterministic way to find an address given specific seeds. - -Recommended workflow: - -1. During Account Initialization derive the PDA using the [`find_program_address`](https://docs.rs/solana-program/latest/solana_program/pubkey/struct.Pubkey.html#method.find_program_address). This will produce PDA along with the canonical bump. The next step is to store the produced bump along with program data in an Account. -2. When using the PDA with different instructions, use [`create_program_address`](https://docs.rs/solana-program/latest/solana_program/pubkey/struct.Pubkey.html#method.create_program_address) with the bump stored in the Account in step 1. - -### Anchor - -Upon initialization, you can use Anchor's `seeds` and `bump` constraint to ensure that PDA derivations in the account validation struct always use the `canonical bump`. - -Anchor allows you to specify a bump with the `bump = ` constraint when verifying the address of a PDA. - -```rust -pub fn _initialize_exchange( - ctx: Context, -) -> Result<()> { - // ... - // Store the produced canonical bump - let escrow = &mut ctx.accounts.escrow; - escrow.bump = ctx.bumps.escrow; - // ... - Ok(()) -} -pub fn _update_exchange( - ctx: Context, -) -> Result<()> { - // Address correctness is ensured in the Context -} - -pub struct InitializeExchange<'info> { - #[account(mut)] - pub side_a: Signer<'info>, - #[account( - init, - payer = side_a, - space = 8 + Escrow::LEN, - seeds = [b"escrow"], // specify desired seeds here - bump // produce canonical bump - )] - pub escrow: Account<'info, Escrow>, - // ... -} -pub struct UpdateExchange<'info> { - #[account( - mut, - seeds = [b"escrow"], // specify desired seeds here - escrow.bump // check the bump against the one already stored - )] - pub escrow: Account<'info, Escrow>, -} -``` - -## Closing Accounts and Revival Attacks - -`Closing an account` improperly creates an opportunity for reinitialization/revival attacks. - -The Solana runtime `garbage collects accounts` when they are no longer rent exempt. Closing accounts involves transferring the lamports stored in the account for rent exemption to another account of your choosing. - -### Plain Rust -The recommended workflow for manually closing an account is to use the Anchor's logic (i.e. check the close [Reference](https://github.com/coral-xyz/anchor/blob/460a16171a715671f77ead5629391c0466366c08/lang/src/common.rs#L6)) - -### Anchor - -You can use the Anchor `#[account(close = )]` constraint to securely close accounts. The close parameter will ([Close Reference](https://github.com/coral-xyz/anchor/blob/460a16171a715671f77ead5629391c0466366c08/lang/src/common.rs#L6)): - -1. `Move` lamports from the data Account to the `` -2. `Assign` ownership of the data Account back to the `System Program` -3. `Realloc` the data size of the Account to 0. - - - -For example: - -```rust -#[derive(Accounts)] -pub struct CancelExchange<'info> { - #[account(mut)] - pub side_a: Signer<'info>, - #[account( - mut, - close = side_a, // this is (i.e. Rent receiver) - seeds = [b"escrow"], - escrow.bump - )] - pub escrow: Account<'info, Escrow>, -} +```bash +npm run dev ``` +Once the frontend is running, it will be available at http://localhost:3000/. ----- - - ### Need help? If you have any questions feel free to reach out to us on [Discord](https://discord.gg/z3JVuZyFnp). diff --git a/7.lesson/bank/.gitignore b/6.lesson/bank/.gitignore similarity index 100% rename from 7.lesson/bank/.gitignore rename to 6.lesson/bank/.gitignore diff --git a/7.lesson/bank/.prettierignore b/6.lesson/bank/.prettierignore similarity index 100% rename from 7.lesson/bank/.prettierignore rename to 6.lesson/bank/.prettierignore diff --git a/7.lesson/bank/Anchor.toml b/6.lesson/bank/Anchor.toml similarity index 100% rename from 7.lesson/bank/Anchor.toml rename to 6.lesson/bank/Anchor.toml diff --git a/7.lesson/bank/Cargo.lock b/6.lesson/bank/Cargo.lock similarity index 100% rename from 7.lesson/bank/Cargo.lock rename to 6.lesson/bank/Cargo.lock diff --git a/7.lesson/bank/Cargo.toml b/6.lesson/bank/Cargo.toml similarity index 100% rename from 7.lesson/bank/Cargo.toml rename to 6.lesson/bank/Cargo.toml diff --git a/7.lesson/bank/migrations/deploy.ts b/6.lesson/bank/migrations/deploy.ts similarity index 100% rename from 7.lesson/bank/migrations/deploy.ts rename to 6.lesson/bank/migrations/deploy.ts diff --git a/7.lesson/bank/package.json b/6.lesson/bank/package.json similarity index 100% rename from 7.lesson/bank/package.json rename to 6.lesson/bank/package.json diff --git a/7.lesson/bank/payer.json b/6.lesson/bank/payer.json similarity index 100% rename from 7.lesson/bank/payer.json rename to 6.lesson/bank/payer.json diff --git a/7.lesson/bank/programs/bank/Cargo.toml b/6.lesson/bank/programs/bank/Cargo.toml similarity index 100% rename from 7.lesson/bank/programs/bank/Cargo.toml rename to 6.lesson/bank/programs/bank/Cargo.toml diff --git a/7.lesson/bank/programs/bank/Xargo.toml b/6.lesson/bank/programs/bank/Xargo.toml similarity index 100% rename from 7.lesson/bank/programs/bank/Xargo.toml rename to 6.lesson/bank/programs/bank/Xargo.toml diff --git a/7.lesson/bank/programs/bank/src/lib.rs b/6.lesson/bank/programs/bank/src/lib.rs similarity index 100% rename from 7.lesson/bank/programs/bank/src/lib.rs rename to 6.lesson/bank/programs/bank/src/lib.rs diff --git a/7.lesson/bank/tests/bank.ts b/6.lesson/bank/tests/bank.ts similarity index 100% rename from 7.lesson/bank/tests/bank.ts rename to 6.lesson/bank/tests/bank.ts diff --git a/7.lesson/bank/tsconfig.json b/6.lesson/bank/tsconfig.json similarity index 100% rename from 7.lesson/bank/tsconfig.json rename to 6.lesson/bank/tsconfig.json diff --git a/7.lesson/bank/yarn.lock b/6.lesson/bank/yarn.lock similarity index 100% rename from 7.lesson/bank/yarn.lock rename to 6.lesson/bank/yarn.lock diff --git a/7.lesson/frontend/Dockerfile b/6.lesson/frontend/Dockerfile similarity index 100% rename from 7.lesson/frontend/Dockerfile rename to 6.lesson/frontend/Dockerfile diff --git a/7.lesson/frontend/LICENSE b/6.lesson/frontend/LICENSE similarity index 100% rename from 7.lesson/frontend/LICENSE rename to 6.lesson/frontend/LICENSE diff --git a/7.lesson/frontend/README.md b/6.lesson/frontend/README.md similarity index 100% rename from 7.lesson/frontend/README.md rename to 6.lesson/frontend/README.md diff --git a/7.lesson/frontend/next-env.d.ts b/6.lesson/frontend/next-env.d.ts similarity index 100% rename from 7.lesson/frontend/next-env.d.ts rename to 6.lesson/frontend/next-env.d.ts diff --git a/7.lesson/frontend/next.config.js b/6.lesson/frontend/next.config.js similarity index 100% rename from 7.lesson/frontend/next.config.js rename to 6.lesson/frontend/next.config.js diff --git a/7.lesson/frontend/package-lock.json b/6.lesson/frontend/package-lock.json similarity index 100% rename from 7.lesson/frontend/package-lock.json rename to 6.lesson/frontend/package-lock.json diff --git a/7.lesson/frontend/package.json b/6.lesson/frontend/package.json similarity index 100% rename from 7.lesson/frontend/package.json rename to 6.lesson/frontend/package.json diff --git a/7.lesson/frontend/postcss.config.js b/6.lesson/frontend/postcss.config.js similarity index 100% rename from 7.lesson/frontend/postcss.config.js rename to 6.lesson/frontend/postcss.config.js diff --git a/7.lesson/frontend/public/favicon.ico b/6.lesson/frontend/public/favicon.ico similarity index 100% rename from 7.lesson/frontend/public/favicon.ico rename to 6.lesson/frontend/public/favicon.ico diff --git a/7.lesson/frontend/public/solanaLogo.png b/6.lesson/frontend/public/solanaLogo.png similarity index 100% rename from 7.lesson/frontend/public/solanaLogo.png rename to 6.lesson/frontend/public/solanaLogo.png diff --git a/7.lesson/frontend/public/vercel.svg b/6.lesson/frontend/public/vercel.svg similarity index 100% rename from 7.lesson/frontend/public/vercel.svg rename to 6.lesson/frontend/public/vercel.svg diff --git a/7.lesson/frontend/scaffold-desktop.png b/6.lesson/frontend/scaffold-desktop.png similarity index 100% rename from 7.lesson/frontend/scaffold-desktop.png rename to 6.lesson/frontend/scaffold-desktop.png diff --git a/7.lesson/frontend/scaffold-mobile.png b/6.lesson/frontend/scaffold-mobile.png similarity index 100% rename from 7.lesson/frontend/scaffold-mobile.png rename to 6.lesson/frontend/scaffold-mobile.png diff --git a/7.lesson/frontend/src/components/AppBar.tsx b/6.lesson/frontend/src/components/AppBar.tsx similarity index 100% rename from 7.lesson/frontend/src/components/AppBar.tsx rename to 6.lesson/frontend/src/components/AppBar.tsx diff --git a/7.lesson/frontend/src/components/Bank.tsx b/6.lesson/frontend/src/components/Bank.tsx similarity index 100% rename from 7.lesson/frontend/src/components/Bank.tsx rename to 6.lesson/frontend/src/components/Bank.tsx diff --git a/7.lesson/frontend/src/components/ContentContainer.tsx b/6.lesson/frontend/src/components/ContentContainer.tsx similarity index 100% rename from 7.lesson/frontend/src/components/ContentContainer.tsx rename to 6.lesson/frontend/src/components/ContentContainer.tsx diff --git a/7.lesson/frontend/src/components/Footer.tsx b/6.lesson/frontend/src/components/Footer.tsx similarity index 100% rename from 7.lesson/frontend/src/components/Footer.tsx rename to 6.lesson/frontend/src/components/Footer.tsx diff --git a/7.lesson/frontend/src/components/NetworkSwitcher.tsx b/6.lesson/frontend/src/components/NetworkSwitcher.tsx similarity index 100% rename from 7.lesson/frontend/src/components/NetworkSwitcher.tsx rename to 6.lesson/frontend/src/components/NetworkSwitcher.tsx diff --git a/7.lesson/frontend/src/components/Notification.tsx b/6.lesson/frontend/src/components/Notification.tsx similarity index 100% rename from 7.lesson/frontend/src/components/Notification.tsx rename to 6.lesson/frontend/src/components/Notification.tsx diff --git a/7.lesson/frontend/src/components/RequestAirdrop.tsx b/6.lesson/frontend/src/components/RequestAirdrop.tsx similarity index 100% rename from 7.lesson/frontend/src/components/RequestAirdrop.tsx rename to 6.lesson/frontend/src/components/RequestAirdrop.tsx diff --git a/7.lesson/frontend/src/components/SendTransaction.tsx b/6.lesson/frontend/src/components/SendTransaction.tsx similarity index 100% rename from 7.lesson/frontend/src/components/SendTransaction.tsx rename to 6.lesson/frontend/src/components/SendTransaction.tsx diff --git a/7.lesson/frontend/src/components/SendVersionedTransaction.tsx b/6.lesson/frontend/src/components/SendVersionedTransaction.tsx similarity index 100% rename from 7.lesson/frontend/src/components/SendVersionedTransaction.tsx rename to 6.lesson/frontend/src/components/SendVersionedTransaction.tsx diff --git a/7.lesson/frontend/src/components/SignMessage.tsx b/6.lesson/frontend/src/components/SignMessage.tsx similarity index 100% rename from 7.lesson/frontend/src/components/SignMessage.tsx rename to 6.lesson/frontend/src/components/SignMessage.tsx diff --git a/7.lesson/frontend/src/components/Text/index.tsx b/6.lesson/frontend/src/components/Text/index.tsx similarity index 100% rename from 7.lesson/frontend/src/components/Text/index.tsx rename to 6.lesson/frontend/src/components/Text/index.tsx diff --git a/7.lesson/frontend/src/components/bank.json b/6.lesson/frontend/src/components/bank.json similarity index 100% rename from 7.lesson/frontend/src/components/bank.json rename to 6.lesson/frontend/src/components/bank.json diff --git a/7.lesson/frontend/src/components/bank.ts b/6.lesson/frontend/src/components/bank.ts similarity index 100% rename from 7.lesson/frontend/src/components/bank.ts rename to 6.lesson/frontend/src/components/bank.ts diff --git a/7.lesson/frontend/src/components/nav-element/index.tsx b/6.lesson/frontend/src/components/nav-element/index.tsx similarity index 100% rename from 7.lesson/frontend/src/components/nav-element/index.tsx rename to 6.lesson/frontend/src/components/nav-element/index.tsx diff --git a/7.lesson/frontend/src/contexts/AutoConnectProvider.tsx b/6.lesson/frontend/src/contexts/AutoConnectProvider.tsx similarity index 100% rename from 7.lesson/frontend/src/contexts/AutoConnectProvider.tsx rename to 6.lesson/frontend/src/contexts/AutoConnectProvider.tsx diff --git a/7.lesson/frontend/src/contexts/ContextProvider.tsx b/6.lesson/frontend/src/contexts/ContextProvider.tsx similarity index 100% rename from 7.lesson/frontend/src/contexts/ContextProvider.tsx rename to 6.lesson/frontend/src/contexts/ContextProvider.tsx diff --git a/7.lesson/frontend/src/contexts/NetworkConfigurationProvider.tsx b/6.lesson/frontend/src/contexts/NetworkConfigurationProvider.tsx similarity index 100% rename from 7.lesson/frontend/src/contexts/NetworkConfigurationProvider.tsx rename to 6.lesson/frontend/src/contexts/NetworkConfigurationProvider.tsx diff --git a/7.lesson/frontend/src/hooks/useQueryContext.tsx b/6.lesson/frontend/src/hooks/useQueryContext.tsx similarity index 100% rename from 7.lesson/frontend/src/hooks/useQueryContext.tsx rename to 6.lesson/frontend/src/hooks/useQueryContext.tsx diff --git a/7.lesson/frontend/src/models/types.ts b/6.lesson/frontend/src/models/types.ts similarity index 100% rename from 7.lesson/frontend/src/models/types.ts rename to 6.lesson/frontend/src/models/types.ts diff --git a/7.lesson/frontend/src/pages/_app.tsx b/6.lesson/frontend/src/pages/_app.tsx similarity index 100% rename from 7.lesson/frontend/src/pages/_app.tsx rename to 6.lesson/frontend/src/pages/_app.tsx diff --git a/7.lesson/frontend/src/pages/_document.tsx b/6.lesson/frontend/src/pages/_document.tsx similarity index 100% rename from 7.lesson/frontend/src/pages/_document.tsx rename to 6.lesson/frontend/src/pages/_document.tsx diff --git a/7.lesson/frontend/src/pages/api/hello.ts b/6.lesson/frontend/src/pages/api/hello.ts similarity index 100% rename from 7.lesson/frontend/src/pages/api/hello.ts rename to 6.lesson/frontend/src/pages/api/hello.ts diff --git a/7.lesson/frontend/src/pages/basics.tsx b/6.lesson/frontend/src/pages/basics.tsx similarity index 100% rename from 7.lesson/frontend/src/pages/basics.tsx rename to 6.lesson/frontend/src/pages/basics.tsx diff --git a/7.lesson/frontend/src/pages/index.tsx b/6.lesson/frontend/src/pages/index.tsx similarity index 100% rename from 7.lesson/frontend/src/pages/index.tsx rename to 6.lesson/frontend/src/pages/index.tsx diff --git a/7.lesson/frontend/src/stores/useNotificationStore.tsx b/6.lesson/frontend/src/stores/useNotificationStore.tsx similarity index 100% rename from 7.lesson/frontend/src/stores/useNotificationStore.tsx rename to 6.lesson/frontend/src/stores/useNotificationStore.tsx diff --git a/7.lesson/frontend/src/stores/useUserSOLBalanceStore.tsx b/6.lesson/frontend/src/stores/useUserSOLBalanceStore.tsx similarity index 100% rename from 7.lesson/frontend/src/stores/useUserSOLBalanceStore.tsx rename to 6.lesson/frontend/src/stores/useUserSOLBalanceStore.tsx diff --git a/7.lesson/frontend/src/styles/globals.css b/6.lesson/frontend/src/styles/globals.css similarity index 100% rename from 7.lesson/frontend/src/styles/globals.css rename to 6.lesson/frontend/src/styles/globals.css diff --git a/7.lesson/frontend/src/utils/explorer.ts b/6.lesson/frontend/src/utils/explorer.ts similarity index 100% rename from 7.lesson/frontend/src/utils/explorer.ts rename to 6.lesson/frontend/src/utils/explorer.ts diff --git a/7.lesson/frontend/src/utils/index.tsx b/6.lesson/frontend/src/utils/index.tsx similarity index 100% rename from 7.lesson/frontend/src/utils/index.tsx rename to 6.lesson/frontend/src/utils/index.tsx diff --git a/7.lesson/frontend/src/utils/notifications.tsx b/6.lesson/frontend/src/utils/notifications.tsx similarity index 100% rename from 7.lesson/frontend/src/utils/notifications.tsx rename to 6.lesson/frontend/src/utils/notifications.tsx diff --git a/7.lesson/frontend/src/views/basics/index.tsx b/6.lesson/frontend/src/views/basics/index.tsx similarity index 100% rename from 7.lesson/frontend/src/views/basics/index.tsx rename to 6.lesson/frontend/src/views/basics/index.tsx diff --git a/7.lesson/frontend/src/views/home/index.tsx b/6.lesson/frontend/src/views/home/index.tsx similarity index 100% rename from 7.lesson/frontend/src/views/home/index.tsx rename to 6.lesson/frontend/src/views/home/index.tsx diff --git a/7.lesson/frontend/src/views/index.tsx b/6.lesson/frontend/src/views/index.tsx similarity index 100% rename from 7.lesson/frontend/src/views/index.tsx rename to 6.lesson/frontend/src/views/index.tsx diff --git a/7.lesson/frontend/tailwind.config.js b/6.lesson/frontend/tailwind.config.js similarity index 100% rename from 7.lesson/frontend/tailwind.config.js rename to 6.lesson/frontend/tailwind.config.js diff --git a/7.lesson/frontend/tsconfig.json b/6.lesson/frontend/tsconfig.json similarity index 100% rename from 7.lesson/frontend/tsconfig.json rename to 6.lesson/frontend/tsconfig.json diff --git a/7.lesson/frontend/yarn.lock b/6.lesson/frontend/yarn.lock similarity index 100% rename from 7.lesson/frontend/yarn.lock rename to 6.lesson/frontend/yarn.lock diff --git a/7.lesson/solana_banks.png b/6.lesson/solana_banks.png similarity index 100% rename from 7.lesson/solana_banks.png rename to 6.lesson/solana_banks.png diff --git a/7.lesson/README.md b/7.lesson/README.md index 26381c5..b8ec760 100644 --- a/7.lesson/README.md +++ b/7.lesson/README.md @@ -1,34 +1,430 @@ -# 7. Lecture - Frontend for Solana Programs +# 7. Lecture - Security -This week's lecture focuses on teaching you how to build a simple frontend for your dApp. +## Table of Contents +- [7. Lecture - Security](#7-lecture---security) + - [Table of Contents](#table-of-contents) +- [Common Solana Attack Vectors](#common-solana-attack-vectors) +- [Solana Runtime Policy](#solana-runtime-policy) + - [Immutability](#immutability) + - [Data](#data) + - [Ownership](#ownership) + - [Zero Balance](#zero-balance) + - [Transaction](#transaction) + - [Data Allocation](#data-allocation) + - [Balance](#balance) + - [Rent](#rent) +- [Best Security Practices](#best-security-practices) + - [Signer Authorization](#signer-authorization) + - [Plain Rust](#plain-rust) + - [Anchor](#anchor) + - [Owner Checks](#owner-checks) + - [Plain Rust](#plain-rust-1) + - [Anchor](#anchor-1) + - [Account Data Matching](#account-data-matching) + - [Plain Rust](#plain-rust-2) + - [Anchor](#anchor-2) + - [Reinitialization Attacks](#reinitialization-attacks) + - [Plain Rust](#plain-rust-3) + - [Anchor](#anchor-3) + - [Duplicate Mutable Accounts](#duplicate-mutable-accounts) + - [Plain Rust](#plain-rust-4) + - [Anchor](#anchor-4) + - [Type Cosplay](#type-cosplay) + - [Plain Rust](#plain-rust-5) + - [Anchor](#anchor-5) + - [Arbitrary CPI](#arbitrary-cpi) + - [Plain Rust](#plain-rust-6) + - [Anchor](#anchor-6) + - [Bump Seed Canonicalization](#bump-seed-canonicalization) + - [Plain Rust](#plain-rust-7) + - [Anchor](#anchor-7) + - [Closing Accounts and Revival Attacks](#closing-accounts-and-revival-attacks) + - [Plain Rust](#plain-rust-8) + - [Anchor](#anchor-8) + - [Need help?](#need-help) +--- -The repository contains the source code for both the Anchor program as well as the frontend of the bank example created during the lecture. +# Common Solana Attack Vectors -**The Anchor program is already deployed**, so you can interact with it by simply starting the frontend and connecting your hot wallet. Make sure your wallet is set to **devnet** mode. +> [!IMPORTANT] +> For the Common Attack Vectors on Solana check the [Common Attack Vectors](https://github.com/Ackee-Blockchain/Solana-Auditors-Bootcamp/tree/master/Lesson-6) created for Solana Auditors Bootcamp. - +# Solana Runtime Policy ->[!TIP] ->### Try Deploying the Program Yourself! ->You will need to update the program ID as demonstrated at the start of the lecture. The current program ID is already in use. +## Immutability +> Executable accounts are fully immutable. +> Executable is one-way (false->true) and only the account owner may set it. -## Setup +## Data -To set up the frontend, navigate to the `frontend` directory and install the dependencies: +> Only the owner of an account may modify its data. -```bash -npm install +> Accounts may only be assigned a new owner if their data is zeroed out. +And only if the account is not executable. And only if the account is writable. + +## Ownership + +> Only the owner of an account may assign a new owner. + +## Zero Balance + +> Accounts with zero balance will be deleted at the end of transaction processing. + +> Temporary accounts with zero balance may be created during a transaction. + +## Transaction + +> Total balances on all the accounts are equal before and after the execution of a transaction. + +> After the transaction is executed, balances of read-only accounts must be equal to the balances before the transaction. + +> All instructions in the transaction are executed atomically. If one fails, all account modifications are discarded. + +## Data Allocation + +> Only the owner may change account size and data. And if the account is writable. And if the account is not executable. + +> Newly allocated account data is always zeroed out. + +## Balance + +> Only the owner of an account may subtract its lamports. + +> Any program account may add lamports to an account. + +## Rent + +> Rent fees are charged every epoch and are determined by account size. + +> Accounts with sufficient balance to cover 2 years of rent are exempt from fees. + + + + + +# Best Security Practices + +## Signer Authorization + +### Plain Rust + +Use `Signer Checks` to verify that specific accounts have signed a transaction. Without appropriate signer checks, accounts may be able to execute instructions they shouldn’t be authorized to perform. + +To implement a signer check in Rust, simply check that an account’s `is_signer` property is `true` + +```rust +if !ctx.accounts.authority.is_signer { + return Err(ProgramError::MissingRequiredSignature.into()); +} +``` + +### Anchor +In Anchor, you can use the `Signer` account type in your account validation struct to have Anchor automatically perform a signer check on a given account + +```rust +#[derive(Accounts)] +pub struct UpdateUserData<'info> { + // performs signer check + pub user: Signer<'info>, +} +``` + +## Owner Checks + +### Plain Rust + +Use `Owner Checks` to verify that accounts are owned by the expected program. Without appropriate owner checks, accounts owned by unexpected programs could be used in an instruction. + +To implement an owner check in Rust, simply check that an account’s owner matches an expected program ID + +```rust +if ctx.accounts.account.owner != ctx.program_id { + return Err(ProgramError::IncorrectProgramId.into()); +} +``` + +### Anchor + +Anchor program account types implement the `Owner` trait which allows the `Account<'info, T>` wrapper to automatically verify program ownership + +```rust +#[derive(Accounts)] +pub struct UpdateUserData<'info> { + pub user: Signer<'info>, + // performs ownership check + pub user_data: Account<'info, UserData>, +} +``` + +Anchor gives you the option to explicitly define the owner of an account if it should be anything other than the currently executing program + +## Account Data Matching + +### Plain Rust + +Use `Data Validation checks` to verify that account data matches an expected value. Without appropriate data validation checks, unexpected accounts may be used in an instruction. + +To implement data validation checks in Rust, simply compare the data stored on an account to an expected value. + +```rust +if ctx.accounts.user.key() != ctx.accounts.user_data.user { + return Err(ProgramError::InvalidAccountData.into()); +} +``` + +### Anchor + +In Anchor, you can use `constraint` to checks whether the given expression evaluates to true. Alternatively, you can use `has_one` to check that a target account field stored on the account matches the key of an account in the `Accounts` struct. + +```rust +#[derive(Accounts)] +pub struct UpdateUserData<'info> { + pub user: Signer<'info>, + #[account(constraint = user_data.authority = user.key())] + pub user_data: Account<'info, UserData>, +} +``` + +## Reinitialization Attacks + +### Plain Rust + +Use an `account discriminator or initialization flag` to check whether an account has already been initialized to prevent an account from being reinitialized and overriding existing account data. + +To prevent account reinitialization in plain Rust, initialize accounts with an `is_initialized` flag and check if it has already been set to true when initializing an account + +```rust +if account.is_initialized { + return Err(ProgramError::AccountAlreadyInitialized.into()); +} +``` + +### Anchor + +To simplify this, use Anchor’s `init` constraint to create an account via a CPI to the system program and sets its discriminator + +```rust +#[derive(Accounts)] +pub struct UpdateUserData<'info> { + pub user: Signer<'info>, + // if account is already initialized init will return error + #[account(init)] + pub user_data: Account<'info, UserData>, +} +``` + +## Duplicate Mutable Accounts + +### Plain Rust + +When an instruction requires two mutable accounts of the same type, an attacker can pass in the same account twice, causing the account to be mutated in unintended ways. + +To check for duplicate mutable accounts in Rust, simply compare the public keys of the two accounts and throw an error if they are the same. + +```rust +if ctx.accounts.account_one.key() == ctx.accounts.account_two.key() { + return Err(ProgramError::InvalidArgument) +} +``` + +### Anchor + +In Anchor, you can use constraint to add an explicit constraint to an account checking that it is not the same as another account. + +```rust +#[derive(Accounts)] +pub struct UpdateUserData<'info> { + pub user: Signer<'info>, + #[account( + mut, + constraint = user_data.key() != different_user_data.key() + )] + pub user_data: Account<'info, UserData>, + #[account(mut)] + pub different_user_data: Account<'info, UserData>, +} ``` -Then, start the frontend with the following command: +## Type Cosplay + +### Plain Rust -```bash -npm run dev +Use discriminators to distinguish between different account types + +To implement a discriminator in Rust, include a field in the account struct to represent the account type + +```rust +#[derive(BorshSerialize, BorshDeserialize)] +pub struct User { + discriminant: AccountDiscriminant, + user: Pubkey, +} + +#[derive(BorshSerialize, BorshDeserialize, PartialEq)] +pub enum AccountDiscriminant { + User, + Admin, +} +``` + +To implement a discriminator check in Rust, verify that the discriminator of the deserialized account data matches the expected value + +```rust +if user.discriminant != AccountDiscriminant::User { + return Err(ProgramError::InvalidAccountData.into()); +} +``` + +### Anchor + +In Anchor, program account types automatically implement the `Discriminator` trait which creates an 8 byte unique identifier for a type + +Use Anchor’s `Account<'info, T>` type to automatically check the discriminator of the account when deserializing the account data + +```rust +#[derive(Accounts)] +pub struct UpdateUserData<'info> { + pub user: Signer<'info>, + // Account type automatically checks the Discriminator + // of the UserData Account + pub user_data: Account<'info, UserData>, +} +``` + +## Arbitrary CPI + +### Plain Rust + +To generate a CPI, the target program must be passed into the invoking instruction as an account. This means that any target program could be passed into the instruction. Your program should check for incorrect or unexpected programs. + +Perform program checks in native programs by simply comparing the public key of the passed-in program to the program you expected. + +```rust +pub fn cpi_secure(ctx: Context, amount: u64) -> Result<()> { + if &spl_token::ID != ctx.accounts.token_program.key { + return Err(ProgramError::IncorrectProgramId); + } + ... + // CPI logic goes here +} +``` + +### Anchor + +If a program is written in Anchor, then it may have a publicly available CPI module. This makes invoking the program from another Anchor program simple and secure. The Anchor CPI module automatically checks that the address of the program passed in matches the address of the program stored in the module. + +The best practice while using Anchor is to always use `Program<'info, T>`, which will check that the account is executable and it is the given Program. For example: + +```rust +use anchor_spl::token::Token; + +#[derive(Accounts)] +pub struct InitializeExchange<'info> { + // ... + pub token_program: Program<'info, Token>, + // ... +} +``` + +## Bump Seed Canonicalization + +### Plain Rust + +The [`create_program_address`](https://docs.rs/solana-program/latest/solana_program/pubkey/struct.Pubkey.html#method.create_program_address) function derives a PDA without searching for the `canonical bump`. This means there may be multiple valid bumps, all of which will produce different addresses. + +Using [`find_program_address`](https://docs.rs/solana-program/latest/solana_program/pubkey/struct.Pubkey.html#method.find_program_address) ensures that the highest valid bump, or canonical bump, is used for the derivation, thus creating a deterministic way to find an address given specific seeds. + +Recommended workflow: + +1. During Account Initialization derive the PDA using the [`find_program_address`](https://docs.rs/solana-program/latest/solana_program/pubkey/struct.Pubkey.html#method.find_program_address). This will produce PDA along with the canonical bump. The next step is to store the produced bump along with program data in an Account. +2. When using the PDA with different instructions, use [`create_program_address`](https://docs.rs/solana-program/latest/solana_program/pubkey/struct.Pubkey.html#method.create_program_address) with the bump stored in the Account in step 1. + +### Anchor + +Upon initialization, you can use Anchor's `seeds` and `bump` constraint to ensure that PDA derivations in the account validation struct always use the `canonical bump`. + +Anchor allows you to specify a bump with the `bump = ` constraint when verifying the address of a PDA. + +```rust +pub fn _initialize_exchange( + ctx: Context, +) -> Result<()> { + // ... + // Store the produced canonical bump + let escrow = &mut ctx.accounts.escrow; + escrow.bump = ctx.bumps.escrow; + // ... + Ok(()) +} +pub fn _update_exchange( + ctx: Context, +) -> Result<()> { + // Address correctness is ensured in the Context +} + +pub struct InitializeExchange<'info> { + #[account(mut)] + pub side_a: Signer<'info>, + #[account( + init, + payer = side_a, + space = 8 + Escrow::LEN, + seeds = [b"escrow"], // specify desired seeds here + bump // produce canonical bump + )] + pub escrow: Account<'info, Escrow>, + // ... +} +pub struct UpdateExchange<'info> { + #[account( + mut, + seeds = [b"escrow"], // specify desired seeds here + escrow.bump // check the bump against the one already stored + )] + pub escrow: Account<'info, Escrow>, +} +``` + +## Closing Accounts and Revival Attacks + +`Closing an account` improperly creates an opportunity for reinitialization/revival attacks. + +The Solana runtime `garbage collects accounts` when they are no longer rent exempt. Closing accounts involves transferring the lamports stored in the account for rent exemption to another account of your choosing. + +### Plain Rust +The recommended workflow for manually closing an account is to use the Anchor's logic (i.e. check the close [Reference](https://github.com/coral-xyz/anchor/blob/460a16171a715671f77ead5629391c0466366c08/lang/src/common.rs#L6)) + +### Anchor + +You can use the Anchor `#[account(close = )]` constraint to securely close accounts. The close parameter will ([Close Reference](https://github.com/coral-xyz/anchor/blob/460a16171a715671f77ead5629391c0466366c08/lang/src/common.rs#L6)): + +1. `Move` lamports from the data Account to the `` +2. `Assign` ownership of the data Account back to the `System Program` +3. `Realloc` the data size of the Account to 0. + + + +For example: + +```rust +#[derive(Accounts)] +pub struct CancelExchange<'info> { + #[account(mut)] + pub side_a: Signer<'info>, + #[account( + mut, + close = side_a, // this is (i.e. Rent receiver) + seeds = [b"escrow"], + escrow.bump + )] + pub escrow: Account<'info, Escrow>, +} ``` -Once the frontend is running, it will be available at http://localhost:3000/. ----- + + ### Need help? If you have any questions feel free to reach out to us on [Discord](https://discord.gg/z3JVuZyFnp).