基础银行教程

本文介绍了如何在Solana上使用Anchor框架构建一个简单的银行程序,包括账户创建、余额查询、存款和取款等基本功能。文章详细讲解了程序中用到的关键概念,例如PDA(Program Derived Address),并通过Solidity代码和Rust代码进行了对比,展示了如何在Solana上实现类似以太坊的功能。

在本教程中,我们将在 Solana 上构建一个简单的银行程序,它具有你期望从普通银行获得的基本功能。用户可以创建账户、查看余额、存入资金,并在需要时提取资金。存入的 SOL 将存储在我们的程序拥有的银行 PDA 中。

以下是我们尝试用 Anchor 构建的 Solidity 表示。

在 Solidity 代码中,我们定义了一个 UserBank 结构体来保存状态。这些反映了我们将在 Solana 程序中创建的单独账户。代码中的 initialize 函数将银行的总存款设置为零,但在 Solana 程序中,这将涉及部署和初始化银行账户本身。类似地,Solidity 代码中的 createUserAccount 函数只是更新存储,而在 Solana 中,它将在链上创建并初始化一个新的用户账户。另请注意存款和取款如何更新用户和银行的状态,自定义错误会在余额不足或无效时强制执行规则。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

contract BasicBank {
    // Custom errors
    error ZeroAmount();
    error InsufficientBalance();
    error Overflow();
    error Underflow();
    error InsufficientFunds();
    error UnauthorizedAccess();

    // Bank struct to track total deposits across all users
    struct Bank {
        uint256 totalDeposits;
    }

    // User account struct to store individual balances
    struct UserAccount {
        address owner;
        uint256 balance;
    }

    // The bank state
    Bank public bank;

    // Mapping from user address to their user account
    mapping(address => UserAccount) public userAccounts;

    // Initialize the bank
    function initialize() external {
        bank.totalDeposits = 0;
    }

    // Create a user account
    function createUserAccount() external {
        // Ensure account doesn't already exist
        require(userAccounts[msg.sender].owner == address(0), "Account already created");

        // Initialize the user account
        userAccounts[msg.sender].owner = msg.sender;
        userAccounts[msg.sender].balance = 0;
    }

    // Deposit ETH into the bank
    function deposit(uint256 amount) external payable {
        // Ensure amount is greater than zero
        if (amount == 0) {
            revert ZeroAmount();
        }

        // Ensure account exists and is owned by caller
        if (userAccounts[msg.sender].owner != msg.sender) {
            revert UnauthorizedAccess();
        }
        // Ensure the correct amount was sent
        require(msg.value == amount, "Amount mismatch");

        // Update user balance with checks for overflow
        uint256 newUserBalance = userAccounts[msg.sender].balance + amount;
        if (newUserBalance < userAccounts[msg.sender].balance) {
            revert Overflow();
        }
        userAccounts[msg.sender].balance = newUserBalance;

        // Update bank total deposits with checks for overflow
        uint256 newTotalDeposits = bank.totalDeposits + amount;
        if (newTotalDeposits < bank.totalDeposits) {
            revert Overflow();
        }
        bank.totalDeposits = newTotalDeposits;
    }

    // Withdraw ETH from the bank
    function withdraw(uint256 amount) external {
        // Ensure amount is greater than zero
        if (amount == 0) {
            revert ZeroAmount();
        }

        // Ensure account exists and is owned by caller
        if (userAccounts[msg.sender].owner != msg.sender) {
            revert UnauthorizedAccess();
        }
        // Check if user has enough balance
        if (userAccounts[msg.sender].balance < amount) {
            revert InsufficientBalance();
        }

        // Update user balance with checks for underflow
        uint256 newBalance = userAccounts[msg.sender].balance - amount;
        userAccounts[msg.sender].balance = newBalance;

        // Update bank total deposits with checks for underflow
        uint256 newTotalDeposits = bank.totalDeposits - amount;
        if (newTotalDeposits > bank.totalDeposits) {
            revert Underflow();
        }
        bank.totalDeposits = newTotalDeposits;

        // Transfer ETH to the user
        (bool success, ) = msg.sender.call{value: amount}("");
        require(success, "Transfer failed");
    }

    // Get the balance of the caller's bank account
    function getBalance() external view returns (uint256) {
        // Ensure account exists and is owned by caller
        if (userAccounts[msg.sender].owner != msg.sender) {
            revert UnauthorizedAccess();
        }
        return userAccounts[msg.sender].balance;
    }
}

创建基础银行程序

在深入研究代码之前,以下是我们的基础银行程序的工作方式。我们需要以下功能:

  • 一种设置和初始化银行的方法
  • 一种供用户创建自己的帐户的方法
  • 一种供用户存入资金的方法
  • 一种供用户提取资金的方法
  • 一种检查余额的方法

我们需要以下存储:

  • 一个中央账户,用于跟踪所有用户的总存款
  • 用户可以创建的个人账户,用于跟踪他们的余额和所有权

所有这些元素结合在一起构成了我们的基础银行程序。

现在,让我们开始吧。

银行 PDA 和用户账户

创建一个名为 basic_bank 的新 Anchor 项目,并添加以下程序代码。该程序定义了两个指令:initialize,它创建一个银行 PDA 来保存存款,以及 create_user_account,它设置一个特定于用户的 PDA 来跟踪每个用户的总存款。用户实际存入的 SOL 将存储在银行 PDA 中。

我们为用户提供了一个专用帐户,因为在 Solana 中,所有程序数据都必须作为其自身的帐户存在。与以太坊不同,我们可以使用映射来存储存款(mapping(address => deposited_amount)),Solana 需要显式帐户分配来存储状态。因此,我们创建一个 user_account PDA 来存储用户的地址和他们的存款金额。


use anchor_lang::prelude::*;
use anchor_lang::solana_program::rent::Rent;
use anchor_lang::solana_program::system_instruction;
use anchor_lang::solana_program::program as solana_program;

declare_id!("u9tNA22L1oRZyF3RKoPVUYTAc1zCYSC5BySKFddZnfN"); // 运行 ANCHOR SYNC 以更新你的程序 ID

#[program]
pub mod basic_bank {
    use super::*;

    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
        // 初始化银行账户
        let bank = &mut ctx.accounts.bank;
        bank.total_deposits = 0;

        msg!("银行已初始化");
        Ok(())
    }

    pub fn create_user_account(ctx: Context<CreateUserAccount>) -> Result<()> {
        // 初始化用户账户
        let user_account = &mut ctx.accounts.user_account;
        user_account.owner = ctx.accounts.user.key();
        user_account.balance = 0;

        msg!("已为以下用户创建账户: {:?}", user_account.owner);
        Ok(())
    }
}

让我们为我们的基础银行程序定义账户结构体。这些结构体被 initializecreate_user_account 函数使用,它们是:

  • Initialize****结构体,包含:

    • bank:正在创建的用于跟踪总存款的新银行账户
    • payer:为交易费用和租金支付的账户
    • system_program:创建新账户所必需的
  • CreateUserAccount 结构体包含:

    • bank:对主银行账户的引用
    • user_account:从用户的公钥派生的 PDA,用于存储他们的余额
    • user:拥有此账户并为其创建付费的签名者
    • system_program:创建账户所必需的
  • 最后,我们定义了一个 Bank 和一个 UserAccount 结构体,它们定义了各自账户的数据结构。Bank 结构体包含一个 total_deposits 字段,用于跟踪银行账户中的所有存款,而 UserAccount 结构体存储用户的公钥和他们在用户账户 PDA 中的个人余额。

// 账户结构体,用于创建银行 PDA 以进行存储
#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(\
        init,\
        payer = payer,\
        space = 8 + Bank::INIT_SPACE)] // 鉴别器 + u64
    pub bank: Account<'info, Bank>,

    #[account(mut)]
    pub payer: Signer<'info>,

    pub system_program: Program<'info, System>,
}

// 用于创建个人用户账户的账户结构体
#[derive(Accounts)]
pub struct CreateUserAccount<'info> {
    #[account(mut)]
    pub bank: Account<'info, Bank>,

    #[account(\
        init,\
        payer = user,\
        space = 8 + UserAccount::INIT_SPACE, // 鉴别器 + 公钥 + u64\
        seeds = [b"user-account", user.key().as_ref()],\
        bump\
    )]
    pub user_account: Account<'info, UserAccount>,

    #[account(mut)]
    pub user: Signer<'info>,

    pub system_program: Program<'info, System>,
}

// 用于跟踪所有用户的总存款的银行账户
#[account]
#[derive(InitSpace)]
pub struct Bank {
    pub total_deposits: u64,
}

// 用于跟踪个人用户余额的特定于用户的账户
#[account]
#[derive(InitSpace)]
pub struct UserAccount {
    pub owner: Pubkey,
    pub balance: u64,
}

接下来,添加以下测试以初始化并创建一个用户账户。

该测试执行以下操作:

  1. 为银行账户生成一个密钥对
  2. 使用提供者的钱包(我们的默认 Anchor 钱包)作为所有操作的签名者
  3. 设置测试金额值(存款 1 SOL,取款 0.5 SOL)
  4. 为签名者的公钥派生一个用户账户的 PDA
  5. 调用 initialize 以设置银行账户并验证它是否以零余额开始。
  6. 调用 createUserAccount 以初始化一个特定于用户的 PDA,并断言用户的地址和余额是否已正确记录。
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { Keypair, PublicKey } from "@solana/web3.js";
import { assert } from "chai";
import { BasicBank } from "../target/types/basic_bank";

describe("basic_bank", () => {
  // 配置客户端以使用本地集群
  anchor.setProvider(anchor.AnchorProvider.env());

  const program = anchor.workspace.BasicBank as Program<BasicBank>;
  const provider = anchor.AnchorProvider.env();

  // 为银行账户生成一个新的密钥对
  const bankAccount = Keypair.generate();

  // 使用提供者的钱包作为签名者
  const signer = provider.wallet;

  // 测试存款金额
  const depositAmount = new anchor.BN(1_000_000_000); // 1 SOL 以 lamports 为单位
  const withdrawAmount = new anchor.BN(500_000_000); // 0.5 SOL 以 lamports 为单位

  // 查找用户账户的 PDA
  const [userAccountPDA] = PublicKey.findProgramAddressSync(
    [Buffer.from("user-account"), signer.publicKey.toBuffer()],
    program.programId
  );

  it("初始化银行帐户", async () => {
    // 初始化银行帐户
    const tx = await program.methods
      .initialize()
      .accounts({
        bank: bankAccount.publicKey,
        payer: signer.publicKey,
        systemProgram: anchor.web3.SystemProgram.programId,
      })
      .signers([bankAccount])
      .rpc();

    console.log("初始化交易签名", tx);

    // 获取银行账户数据
    const bankData = await program.account.bank.fetch(bankAccount.publicKey);

    // 验证银行是否已正确初始化
    assert.equal(bankData.totalDeposits.toString(), "0");
  });

  it("创建一个用户账户", async () => {
    // 为签名者创建用户账户
    const tx = await program.methods
      .createUserAccount()
      .accounts({
        bank: bankAccount.publicKey,
        userAccount: userAccountPDA,
        user: signer.publicKey,
        systemProgram: anchor.web3.SystemProgram.programId,
      })
      .rpc();

    console.log("创建用户账户交易签名", tx);

    // 获取用户账户数据
    const userAccountData = await program.account.userAccount.fetch(userAccountPDA);

    // 验证用户账户是否已正确设置
    assert.equal(userAccountData.owner.toString(), signer.publicKey.toString());
    assert.equal(userAccountData.balance.toString(), "0");
  });
});

运行测试,它应该通过。

一个测试,表明账户创建成功

现在,我们将实现代码以将资金存入银行,并为其编写测试。

将 SOL 存入银行

将以下存款函数添加到我们的程序中。

它执行以下操作:

  1. 验证存款金额是否大于零
  2. 创建并执行系统 transfer 指令(通过 CPI)以将 SOL 从用户的钱包提取到银行账户
  3. 通过将存款金额添加到他们的 user_account PDA,安全地增加用户的账户余额
  4. 以相同金额更新银行的总存款
  5. 记录存款金额和用户地址

    pub fn deposit(ctx: Context<Deposit>, amount: u64) -> Result<()> {
        // 确保存款金额大于零
        require!(amount > 0, BankError::ZeroAmount);

        let user = &ctx.accounts.user.key();
        let bank = &ctx.accounts.bank.key();

        // 使用系统程序将 SOL 从用户转移到银行账户
        let transfer_ix = system_instruction::transfer(user, bank, amount);
        solana_program::invoke(
            &[\
                transfer_ix,\
                ctx.accounts.user.to_account_info(),\
                ctx.accounts.bank.to_account_info(),\
            ],
        )?;

        // 更新用户余额
        let user_account = &mut ctx.accounts.user_account;
        user_account.balance = user_account
            .balance
            .checked_add(amount)
            .ok_or(BankError::Overflow)?;

        // 更新银行存款总额
        let bank = &mut ctx.accounts.bank;
        bank.total_deposits = bank
            .total_deposits
            .checked_add(amount)
            .ok_or(BankError::Overflow)?;

        msg!("为 {} 存入了 {} lamports", amount, user);
        Ok(())
    }

请注意我们如何使用系统 transfer 指令将 SOL 从用户钱包提取到银行,我们稍后将重新讨论此模式。

现在,添加 Deposit 账户结构体

#[derive(Accounts)]
pub struct Deposit<'info> {
    #[account(mut)]
    pub bank: Account<'info, Bank>,

    #[account(\
        mut,\
        seeds = [b"user-account", user.key().as_ref()],\
        bump,\
        constraint = user_account.owner == user.key() @ BankError::UnauthorizedAccess // 确保签名者拥有该账户\
    )]
    pub user_account: Account<'info, UserAccount>,

    #[account(mut)]
    pub user: Signer<'info>,

    pub system_program: Program<'info, System>,
}

添加自定义 BankError


#[error_code]
pub enum BankError {
    #[msg("金额必须大于零")]
    ZeroAmount,

    #[msg("提款余额不足")]
    InsufficientBalance,

    #[msg("算术溢出")]
    Overflow,

    #[msg("算术下溢")]
    Underflow,

    #[msg("银行帐户资金不足")]
    InsufficientFunds,

    #[msg("未经授权访问用户账户")]
    UnauthorizedAccess,
}

现在使用以下测试块更新程序测试。

存款测试执行以下操作:

  1. 它记录用户和银行账户的初始 SOL 余额
  2. 提交一笔从用户到银行的 1 SOL 存款交易
  3. 验证用户账户的余额记录和银行的总存款记录是否都已使用新的存款金额正确更新
  4. 检查银行余额是否增加以及用户余额是否适当减少(考虑到交易费用),以确认实际 SOL 转移已发生
  5. 最后,它会记录所有余额更改

  it("将资金存入银行", async () => {
    // 获取初始 SOL 余额
    const initialUserBalance = await provider.connection.getBalance(signer.publicKey);
    const initialBankBalance = await provider.connection.getBalance(bankAccount.publicKey);

    console.log(`初始用户 SOL 余额:${initialUserBalance / 1e9} SOL`);
    console.log(`初始银行 SOL 余额:${initialBankBalance / 1e9} SOL`);

    // 将资金存入银行
    const tx = await program.methods
      .deposit(depositAmount)
      .accounts({
        bank: bankAccount.publicKey,
        userAccount: userAccountPDA,
        user: signer.publicKey,
        systemProgram: anchor.web3.SystemProgram.programId,
      })
      .rpc();

    console.log("存款交易签名", tx);

    // 获取用户的帐户余额
    const userAccountData = await program.account.userAccount.fetch(userAccountPDA);

    // 验证跟踪的余额是否正确
    assert.equal(userAccountData.balance.toString(), depositAmount.toString());

    // 验证银行跟踪的总存款
    const bankData = await program.account.bank.fetch(bankAccount.publicKey);
    assert.equal(bankData.totalDeposits.toString(), depositAmount.toString());

    // 获取最终 SOL 余额
    const finalUserBalance = await provider.connection.getBalance(signer.publicKey);
    const finalBankBalance = await provider.connection.getBalance(bankAccount.publicKey);

    console.log(`最终用户 SOL 余额:${finalUserBalance / 1e9} SOL`);
    console.log(`最终银行 SOL 余额:${finalBankBalance / 1e9} SOL`);

    // 检查实际 SOL 转移 (考虑交易费用)
    assert.isTrue(finalBankBalance > initialBankBalance);

    // 用户余额应减少存款金额 + 一些交易费用
    assert.isTrue(finalUserBalance < initialUserBalance - Number(depositAmount));
    assert.isTrue(finalUserBalance > initialUserBalance - Number(depositAmount) - 10000); // 考虑到合理的 tx 费用
});

运行测试,它会通过。

屏幕截图显示存款成功

接下来,我们将添加一个函数来检索用户在我们银行中的存款金额。

获取用户余额

以下 get_balance 函数检索并返回用户的 lamport 余额。


    pub fn get_balance(ctx: Context<GetBalance>) -> Result<u64> {
        // 获取用户账户
        let user_account = &ctx.accounts.user_account;
        let balance = user_account.balance;

        msg!("{} 的余额:{} lamports", user_account.owner, balance);
        Ok(balance)
    }

添加 GetBalance 账户结构体。


#[derive(Accounts)]
pub struct GetBalance<'info> {
    pub bank: Account<'info, Bank>,

    #[account(\
        seeds = [b"user-account", user.key().as_ref()],\
        bump,\
        constraint = user_account.owner == user.key() @ BankError::UnauthorizedAccess // 确保签名者拥有该账户\
    )]
    pub user_account: Account<'info, UserAccount>,

    pub user: Signer<'info>,
}

为该函数添加测试。

该测试调用我们的 Anchor 程序的 getBalance 函数,并断言返回的余额等于我们之前存入的金额(1 SOL)。


  it("检索用户余额", async () => {
    // 获取用户的余额
    const balance = await program.methods
      .getBalance()
      .accounts({
        bank: bankAccount.publicKey,
        userAccount: userAccountPDA,
        user: signer.publicKey,
      })
      .view(); // Anchor 中的 .view() 方法用于调用仅读取数据(视图函数)而不提交实际交易的指令

    // 验证余额是否正确
    assert.equal(balance.toString(), depositAmount.toString());

    console.log(`用户余额:${Number(balance) / 1e9} SOL`);
  });

运行测试,它通过。

一个测试,表明已成功读取余额

现在,我们将添加一个提款实现并为其编写测试。

从银行提取余额:成功案例

添加以下代码以从我们的银行中提取用户存款。

withdraw 函数执行以下操作:

  1. 验证提款金额是否大于零
  2. 检查用户是否有足够的余额进行提款
  3. 使用检查的算术更新用户账户余额和银行的总存款
  4. 计算保持账户免租金所需的最低余额
  5. 确定保留免租金最低限额的安全转移金额
  6. 使用直接 lamport 操作转移 SOL(因为程序拥有这些账户)
  7. 记录提款详细信息

    pub fn withdraw(ctx: Context<Withdraw>, amount: u64) -> Result<()> {
        // 确保提款金额大于零
        require!(amount > 0, BankError::ZeroAmount);

        // 获取账户
        let bank = &mut ctx.accounts.bank;
        let user_account = &mut ctx.accounts.user_account;
        let user = ctx.accounts.user.key();

        // 检查用户是否有足够的余额
        require!(
            user_account.balance >= amount,
            BankError::InsufficientBalance
        );

        // 更新用户余额
        user_account.balance = user_account
            .balance
            .checked_sub(amount)
            .ok_or(BankError::Underflow)?;

        // 更新银行存款总额
        bank.total_deposits = bank
            .total_deposits
            .checked_sub(amount)
            .ok_or(BankError::Underflow)?;

        // 计算保持账户免租金所需的最低余额
        let rent = Rent::get()?;
        let user_account_info = ctx.accounts.user_account.to_account_info();
        let minimum_balance = rent.minimum_balance(user_account_info.data_len());

        // 计算安全转移金额 (保留免租金最低限额)
        let available_lamports = user_account_info.lamports();
        let transfer_amount = amount.min(available_lamports.saturating_sub(minimum_balance));

        // 转移 SOL:从用户账户 PDA 中减去,并添加到用户钱包中
        **user_account_info.try_borrow_mut_lamports()? -= transfer_amount;
        **ctx.accounts.user.try_borrow_mut_lamports()? += transfer_amount;

        msg!("为 {} 提取了 {} lamports", amount, user);
        Ok(())
    }

deposit函数中回想一下,我们使用系统程序的 transfer 函数将 SOL 从用户的账户提取到银行中。我们这样做是因为系统程序拥有所有常规钱包(如以太坊中的 EOA),并且有权修改其余额。

屏幕截图显示了 CPI 转移指令

但是在 withdraw 函数中,我们可以直接修改银行和用户账户 PDA 的 lamport 余额。这是因为我们的程序拥有这两个 PDA(我们部署了它们)。

屏幕截图显示了 lamports 的余额已直接修改

现在添加 Withdraw 账户结构体


#[derive(Accounts)]
pub struct Withdraw<'info> {
    #[account(mut)]
    pub bank: Account<'info, Bank>,

    #[account(\
        mut,\
        seeds = [b"user-account", user.key().as_ref()],\
        bump,\
        constraint = user_account.owner == user.key() @ BankError::UnauthorizedAccess\
    )]
    pub user_account: Account<'info, UserAccount>,

    #[account(mut)]
    pub user: Signer<'info>,

    pub system_program: Program<'info, System>,
}

为 withdraw 函数添加测试。

它执行以下操作:

  1. 它记录用户账户的初始余额,并获取用户和银行的 SOL 余额
  2. 提交一笔从银行到用户的 0.5 SOL 提款交易
  3. 验证用户账户余额和银行的总存款是否在提款后已正确更新
  4. 检查用户余额是否增加(考虑到交易费用)以确认实际 SOL 转移已发生
  5. 最后,我们会记录交易的所有余额更改

  it("从银行提取资金", async () => {
    // 获取初始余额
    const userAccountData = await program.account.userAccount.fetch(userAccountPDA);
    const initialBalance = userAccountData.balance;

    // 获取初始 SOL 余额
    const initialUserBalance = await provider.connection.getBalance(signer.publicKey);
    const initialBankBalance = await provider.connection.getBalance(bankAccount.publicKey);

    console.log(`初始用户 SOL 余额:${initialUserBalance / 1e9} SOL`);
    console.log(`初始银行 SOL 余额:${initialBankBalance / 1e9} SOL`);

    // 从银行提取资金
    const tx = await program.methods
      .withdraw(withdrawAmount)
      .accounts({
        bank: bankAccount.publicKey,
        userAccount: userAccountPDA,
        user: signer.publicKey,
        systemProgram: anchor.web3.SystemProgram.programId,
      })
      .rpc();

    console.log("提款交易签名", tx);

    // 获取新余额
    const updatedUserAccountData = await program.account.userAccount.fetch(userAccountPDA);
    const newBalance = updatedUserAccountData.balance;

    // 验证余额是否正确
    const expectedBalance = initialBalance.sub(withdrawAmount);
    assert.equal(newBalance.toString(), expectedBalance.toString());

    // 验证银行总存款
    const bankData = await program.account.bank.fetch(bankAccount.publicKey);
    assert.equal(bankData.totalDeposits.toString(), expectedBalance.toString());

    // 获取最终 SOL 余额
    const finalUserBalance = await provider.connection.getBalance(signer.publicKey);
    const finalBankBalance = await provider.connection.getBalance(bankAccount.publicKey);

    console.log(`最终用户 SOL 余额:${finalUserBalance / 1e9} SOL`);
    console.log(`最终银行 SOL 余额:${finalBankBalance / 1e9} SOL`);

    // 检查实际 SOL 转移
    // 用户余额应增加提款金额 (减去 tx 费用)
    // 由于用户支付 tx 费用,因此最终余额可能略低于预期
    assert.isTrue(finalUserBalance < initialUserBalance + Number(withdrawAmount));
    assert.isTrue(finalUserBalance > initialUserBalance - 10000); // 允许合理的 tx 费用

    // 银行余额应减少提款金额
    assert.isTrue(finalBankBalance <= initialBankBalance);
  });

现在运行测试。它通过。

屏幕截图显示资金已成功提取

从银行提取余额:失败案例

让我们添加另一个测试块,以确认用户无法提取超过他们存入的金额。

添加以下代码,它执行以下操作:

  1. 它尝试从银行提取过多的金额 (10 SOL)
  2. 期望交易因余额不足错误而失败
  3. 使用 try/catch 块来捕获预期的失败并记录收到的实际错误消息
  4. 验证错误是否包含 "余额不足" 文本
  5. 如果未抛出任何错误(这将表明缺少验证 — 我们不希望这样做),则测试失败

  it("阻止用户提取超过其余额的金额", async () => {
    // 尝试提取超过余额的金额
    const excessiveWithdrawAmount = new anchor.BN(10_000_000); // 10 SOL

    try {
      await program.methods
        .withdraw(excessiveWithdrawAmount)
        .accounts({
          bank: bankAccount.publicKey,
          userAccount: userAccountPDA,
          user: signer.publicKey,
          systemProgram: anchor.web3.SystemProgram.programId,
        })
        .rpc();

      // 如果我们到达这里,则测试失败
      assert.fail("应该为余额不足抛出一个错误");
    } catch (error) {
      // 记录实际错误
      console.log("收到的错误:", error.toString());

      // 检查可能表明余额不足的多个可能错误消息
      const errorMsg = error.toString().toLowerCase();
      assert.isTrue(
        errorMsg.includes("insufficient balance") ||
        errorMsg.includes("0x7d3")
      );
    }
  });

最后,运行测试。它通过。

屏幕截图显示用户无法提取超过其余额的金额

我们的基础银行程序到此结束。

本文是 Solana 上的教程系列 的一部分。

  • 原文链接: rareskills.io/post/spl-t...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
RareSkills
RareSkills
https://www.rareskills.io/