在 Solana 中,智能合约(也称为链上程序或 Program)主要是使用 Rust 语言编写的。为了实现一个转账功能,并带有黑白名单限制,我们需要创建一个智能合约,该合约能够接收转账请求,并根据预设的黑白名单规则来决定是否允许转账。
下面是一个简单的 Rust 代码示例,展示了如何在 Solana 上实现这样的智能合约。这个示例假设已经熟悉了 Solana 的基本概念和 Rust 语言的基本语法。
步骤 1: 安装和设置环境
确保已经安装了 Solana CLI 和 Rust 工具链。可以通过 Solana 官方文档来获取这些工具。
# 对于 macOS 和 Linux
sh <(curl -sSfL https://release.solana.com/v1.10.21/install)
# 或者对于 Windows
powershell -Command "iex ((New-Object System.Net.WebClient).DownloadString('https://release.solana.com/v1.10.21/install.ps1'))"
步骤 2: 创建一个新的 Solana 程序
使用 Solana CLI 创建一个新的 Rust 程序项目:
solana program init --template rust
cd my_program
步骤 3: 编写智能合约代码
打开 src/lib.rs 文件,并替换其中的内容为以下代码:
use solana_program::{
account_info::AccountInfo,
entrypoint::{program_error::PrintProgramError, ProgramResult},
msg,
program::invoke,
program_error::ProgramError,
pubkey::Pubkey,
system_instruction,
sysvar::Sysvar,
};
// 定义转账指令的参数结构
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct TransferArgs {
pub amount: u64,
}
// 定义账户元数据
#[derive(Accounts)]
pub struct TransferContext<'info> {
#[account(mut)]
pub from: AccountInfo<'info>,
#[account(mut)]
pub to: AccountInfo<'info>,
pub system_program: AccountInfo<'info>,
}
/// 转账指令处理器
pub fn process_transfer(
program_id: &Pubkey,
accounts: TransferContext,
transfer_args: TransferArgs,
) -> ProgramResult {
let TransferContext {
from,
to,
system_program,
} = accounts;
// 检查转账金额是否大于0
if transfer_args.amount == 0 {
msg!("Amount must be greater than zero");
return Err(ProgramError::InvalidArgument);
}
// 检查黑名单
if is_blacklisted(from.key) {
msg!("Account is blacklisted");
return Err(ProgramError::InvalidAccountData);
}
// 检查白名单
if !is_whitelisted(to.key) {
msg!("Recipient is not whitelisted");
return Err(ProgramError::InvalidAccountData);
}
// 执行转账
invoke(
&system_instruction::transfer(from.key, to.key, transfer_args.amount),
&[from.clone(), to.clone(), system_program.clone()],
)?;
Ok(())
}
/// 检查账户是否在黑名单中
fn is_blacklisted(account: &Pubkey) -> bool {
// 这里应该有一个真正的黑名单列表
let blacklist = vec![
Pubkey::new_unique(),
Pubkey::new_unique(),
];
blacklist.contains(account)
}
/// 检查账户是否在白名单中
fn is_whitelisted(account: &Pubkey) -> bool {
// 这里应该有一个真正的白名单列表
let whitelist = vec![
Pubkey::new_unique(),
Pubkey::new_unique(),
];
whitelist.contains(account)
}
// 入口点
solana_program::entrypoint!(process_instruction);
// 处理指令
fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
let accounts_iter = &mut accounts.iter();
// 解析指令数据
let transfer_args = TransferArgs::unpack_from_slice(instruction_data)?;
// 解析账户元数据
let accounts = TransferContext::try_from_iter(accounts_iter)?;
// 处理转账指令
process_transfer(program_id, accounts, transfer_args)
}
步骤 4: 编译和部署
使用 Solana CLI 编译并部署智能合约:
solana program build
solana program deploy target/deploy/my_program.so
步骤 5: 测试智能合约
可以使用 Solana 的 solana-keygen 或者 spl-token 工具来创建账户和测试转账功能。确保在测试之前向账户充值足够的 SOL 以便进行转账操作。
注意事项
在实际应用中,黑名单和白名单应该存储在一个更安全的地方,比如使用链上存储或其他方式。
本示例简化了许多细节,例如错误处理和账户验证。
确保在生产环境中对所有输入进行严格的验证和错误处理。
以上代码提供了一个基本的框架,可以在此基础上扩展更多的功能和安全性检查。
步骤 6: 扩展功能和安全性
接下来,我们可以进一步完善和扩展智能合约的功能,以增强其安全性和实用性。以下是一些建议:
增加账户权限管理:
可以为账户分配不同的角色,如管理员、普通用户等。
管理员账户可以修改黑名单和白名单。
动态更新黑白名单:
允许管理员账户动态地添加或删除黑名单和白名单中的账户。
增加日志记录:
记录每次转账的操作详情,包括转账金额、时间戳、发起者和接收者的账户信息等。
增加余额检查:
在转账前检查发起者账户是否有足够的余额。
增加交易限额:
限制单次转账的最大金额。
增加多重签名支持:
对于大额交易,可以要求多个账户共同确认。
示例代码扩展
1. 动态更新黑白名单
首先,我们需要在智能合约中增加一个管理员账户,以及相关的指令来更新黑白名单。
use solana_program::{
account_info::AccountInfo,
entrypoint::{program_error::PrintProgramError, ProgramResult},
msg,
program::invoke,
program_error::ProgramError,
pubkey::Pubkey,
system_instruction,
sysvar::Sysvar,
};
// 定义转账指令的参数结构
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct TransferArgs {
pub amount: u64,
}
// 定义更新黑白名单的参数结构
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct UpdateListArgs {
pub account: Pubkey,
pub is_blacklist: bool,
}
// 定义账户元数据
#[derive(Accounts)]
pub struct TransferContext<'info> {
#[account(mut)]
pub from: AccountInfo<'info>,
#[account(mut)]
pub to: AccountInfo<'info>,
pub system_program: AccountInfo<'info>,
pub admin: AccountInfo<'info>, // 新增管理员账户
}
impl<'info> TransferContext<'info> {
pub fn try_from_iter(accounts_iter: &mut std::iter::Peekable<std::slice::Iter<'info, AccountInfo<'info>>>) -> ProgramResult {
let from = accounts_iter.next().ok_or(ProgramError::MissingAccount)?;
let to = accounts_iter.next().ok_or(ProgramError::MissingAccount)?;
let system_program = accounts_iter.next().ok_or(ProgramError::MissingAccount)?;
let admin = accounts_iter.next().ok_or(ProgramError::MissingAccount)?;
Ok(TransferContext {
from: from.clone(),
to: to.clone(),
system_program: system_program.clone(),
admin: admin.clone(),
})
}
}
/// 转账指令处理器
pub fn process_transfer(
program_id: &Pubkey,
accounts: TransferContext,
transfer_args: TransferArgs,
) -> ProgramResult {
let TransferContext {
from,
to,
system_program,
admin,
} = accounts;
// 检查转账金额是否大于0
if transfer_args.amount == 0 {
msg!("Amount must be greater than zero");
return Err(ProgramError::InvalidArgument);
}
// 检查黑名单
if is_blacklisted(from.key, admin) {
msg!("Account is blacklisted");
return Err(ProgramError::InvalidAccountData);
}
// 检查白名单
if !is_whitelisted(to.key, admin) {
msg!("Recipient is not whitelisted");
return Err(ProgramError::InvalidAccountData);
}
// 执行转账
invoke(
&system_instruction::transfer(from.key, to.key, transfer_args.amount),
&[from.clone(), to.clone(), system_program.clone()],
)?;
Ok(())
}
/// 更新黑白名单指令处理器
pub fn process_update_list(
program_id: &Pubkey,
accounts: TransferContext,
update_args: UpdateListArgs,
) -> ProgramResult {
let TransferContext {
admin,
..
} = accounts;
// 检查是否为管理员账户
if !admin_is_admin(admin.key) {
msg!("Not an admin account");
return Err(ProgramError::InvalidAccountData);
}
// 更新黑白名单
if update_args.is_blacklist {
add_to_blacklist(update_args.account);
} else {
add_to_whitelist(update_args.account);
}
Ok(())
}
/// 检查账户是否在黑名单中
fn is_blacklisted(account: &Pubkey, admin: &AccountInfo) -> bool {
// 这里应该有一个真正的黑名单列表
let blacklist = get_blacklist(admin);
blacklist.contains(account)
}
/// 检查账户是否在白名单中
fn is_whitelisted(account: &Pubkey, admin: &AccountInfo) -> bool {
// 这里应该有一个真正的白名单列表
let whitelist = get_whitelist(admin);
whitelist.contains(account)
}
/// 获取黑名单列表
fn get_blacklist(admin: &AccountInfo) -> Vec<Pubkey> {
// 这里应该从管理员账户读取黑名单列表
vec![
Pubkey::new_unique(),
Pubkey::new_unique(),
]
}
/// 获取白名单列表
fn get_whitelist(admin: &AccountInfo) -> Vec<Pubkey> {
// 这里应该从管理员账户读取白名单列表
vec![
Pubkey::new_unique(),
Pubkey::new_unique(),
]
}
/// 检查是否为管理员账户
fn admin_is_admin(account: &Pubkey) -> bool {
// 这里应该有一个真正的管理员账户列表
let admins = vec![
Pubkey::new_unique(),
Pubkey::new_unique(),
];
admins.contains(account)
}
/// 添加账户到黑名单
fn add_to_blacklist(account: Pubkey) {
// 这里应该更新黑名单列表
}
/// 添加账户到白名单
fn add_to_whitelist(account: Pubkey) {
// 这里应该更新白名单列表
}
// 入口点
solana_program::entrypoint!(process_instruction);
// 处理指令
fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
let accounts_iter = &mut accounts.iter();
match instruction_data[0] {
0 => {
let transfer_args = TransferArgs::unpack_from_slice(&instruction_data[1..])?;
let accounts = TransferContext::try_from_iter(accounts_iter)?;
process_transfer(program_id, accounts, transfer_args)
},
1 => {
let update_args = UpdateListArgs::unpack_from_slice(&instruction_data[1..])?;
let accounts = TransferContext::try_from_iter(accounts_iter)?;
process_update_list(program_id, accounts, update_args)
},
_ => Err(ProgramError::InvalidInstructionData),
}
}
步骤 7: 测试扩展功能
可以使用 Solana 的命令行工具来测试这些新的功能。确保在测试之前已经部署了最新的智能合约版本。
测试转账功能:
使用管理员账户添加一些账户到黑白名单。
测试正常转账和黑名单、白名单限制下的转账。
测试更新黑白名单功能:
使用管理员账户尝试添加和删除账户到黑白名单。
检查更新是否生效。
注意事项
在实际应用中,黑白名单应该存储在一个安全的地方,并且需要考虑数据的持久化问题。
确保在生产环境中对所有输入进行严格的验证和错误处理。
在处理敏感操作时,如更新黑白名单,应确保只有授权的管理员账户可以执行这些操作。
以上示例代码提供了一个基础框架,可以在此基础上进一步扩展和完善功能。
测试代码
为了测试上述智能合约的功能,我们可以编写一些简单的测试脚本来模拟转账和更新黑白名单的操作。这里我们将使用 Rust 语言和 Solana 提供的测试框架来进行测试。
步骤 1: 设置测试环境
确保你的项目中包含了测试依赖项。在 Cargo.toml 文件中添加以下内容:
[dependencies]
solana-program-test = "0.2"
solana-sdk = "1.14"
[dev-dependencies]
solana-program-test = "0.2"
solana-sdk = "1.14"
步骤 2: 编写测试脚本
创建一个名为 tests.rs 的文件,并添加以下测试代码:
use solana_program_test::*;
use solana_sdk::{
signature::{Keypair, Signer},
transaction::Transaction,
system_instruction,
system_program,
pubkey::Pubkey,
};
use my_program; // 替换为你的智能合约模块名称
// 测试用的账户密钥对
let alice = Keypair::new();
let bob = Keypair::new();
let admin = Keypair::new();
let blacklisted_account = Keypair::new();
let whitelisted_account = Keypair::new();
// 测试用的转账金额
const TRANSFER_AMOUNT: u64 = 100;
#[tokio::test]
async fn test_transfer() {
let mut test_context = TestContext::new().await;
let program_id = test_context.add_program("my_program", my_program::id(), None).await;
// 初始化账户
initialize_accounts(&mut test_context, &program_id).await;
// 测试正常转账
test_normal_transfer(&mut test_context, &program_id).await.unwrap();
// 测试黑名单转账
test_blacklist_transfer(&mut test_context, &program_id).await.unwrap();
// 测试白名单转账
test_whitelist_transfer(&mut test_context, &program_id).await.unwrap();
}
// 初始化账户
async fn initialize_accounts(test_context: &mut TestContext, program_id: &Pubkey) {
// 给 Alice 和 Bob 账户各充 1000 SOL
test_context
.bank_client
.airdrop(&alice.pubkey(), 1000)
.await
.unwrap();
test_context
.bank_client
.airdrop(&bob.pubkey(), 1000)
.await
.unwrap();
// 将 Alice 设置为管理员
set_admin(&mut test_context, &program_id, &alice.pubkey()).await.unwrap();
// 将 blacklisted_account 添加到黑名单
add_to_blacklist(&mut test_context, &program_id, &blacklisted_account.pubkey()).await.unwrap();
// 将 whitelisted_account 添加到白名单
add_to_whitelist(&mut test_context, &program_id, &whitelisted_account.pubkey()).await.unwrap();
}
// 测试正常转账
async fn test_normal_transfer(test_context: &mut TestContext, program_id: &Pubkey) -> Result<(), solana_program::program_error::ProgramError> {
let tx = Transaction::new_signed_with_payer(
&[system_instruction::transfer(&alice.pubkey(), &bob.pubkey(), TRANSFER_AMOUNT)],
Some(&alice.pubkey()),
&[&alice],
test_context.last_blockhash,
);
test_context
.bank_client
.process_transaction(tx)
.await
.map_err(|_| solana_program::program_error::ProgramError::InvalidAccountData)?;
Ok(())
}
// 测试黑名单转账
async fn test_blacklist_transfer(test_context: &mut TestContext, program_id: &Pubkey) -> Result<(), solana_program::program_error::ProgramError> {
let tx = Transaction::new_signed_with_payer(
&[system_instruction::transfer(&blacklisted_account.pubkey(), &bob.pubkey(), TRANSFER_AMOUNT)],
Some(&blacklisted_account.pubkey()),
&[&blacklisted_account],
test_context.last_blockhash,
);
assert!(test_context
.bank_client
.process_transaction(tx)
.await
.is_err());
Ok(())
}
// 测试白名单转账
async fn test_whitelist_transfer(test_context: &mut TestContext, program_id: &Pubkey) -> Result<(), solana_program::program_error::ProgramError> {
let tx = Transaction::new_signed_with_payer(
&[system_instruction::transfer(&alice.pubkey(), &whitelisted_account.pubkey(), TRANSFER_AMOUNT)],
Some(&alice.pubkey()),
&[&alice],
test_context.last_blockhash,
);
test_context
.bank_client
.process_transaction(tx)
.await
.map_err(|_| solana_program::program_error::ProgramError::InvalidAccountData)?;
Ok(())
}
// 设置管理员账户
async fn set_admin(test_context: &mut TestContext, program_id: &Pubkey, admin_key: &Pubkey) -> Result<(), solana_program::program_error::ProgramError> {
let tx = Transaction::new_signed_with_payer(
&[my_program::instruction::set_admin(*program_id, *admin_key)],
Some(&admin.pubkey()),
&[&admin],
test_context.last_blockhash,
);
test_context
.bank_client
.process_transaction(tx)
.await
.map_err(|_| solana_program::program_error::ProgramError::InvalidAccountData)?;
Ok(())
}
// 添加账户到黑名单
async fn add_to_blacklist(test_context: &mut TestContext, program_id: &Pubkey, account_key: &Pubkey) -> Result<(), solana_program::program_error::ProgramError> {
let tx = Transaction::new_signed_with_payer(
&[my_program::instruction::add_to_blacklist(*program_id, *account_key)],
Some(&admin.pubkey()),
&[&admin],
test_context.last_blockhash,
);
test_context
.bank_client
.process_transaction(tx)
.await
.map_err(|_| solana_program::program_error::ProgramError::InvalidAccountData)?;
Ok(())
}
// 添加账户到白名单
async fn add_to_whitelist(test_context: &mut TestContext, program_id: &Pubkey, account_key: &Pubkey) -> Result<(), solana_program::program_error::ProgramError> {
let tx = Transaction::new_signed_with_payer(
&[my_program::instruction::add_to_whitelist(*program_id, *account_key)],
Some(&admin.pubkey()),
&[&admin],
test_context.last_blockhash,
);
test_context
.bank_client
.process_transaction(tx)
.await
.map_err(|_| solana_program::program_error::ProgramError::InvalidAccountData)?;
Ok(())
}
步骤 3: 更新智能合约代码 (续)
接下来,我们将在智能合约中添加用于测试的指令,如设置管理员、添加到黑名单和白名单等。
1. 更新智能合约代码
首先,在智能合约中定义新的指令处理器,用于设置管理员、添加到黑名单和白名单等操作。
use solana_program::{
account_info::AccountInfo,
entrypoint::{program_error::PrintProgramError, ProgramResult},
msg,
program::invoke,
program_error::ProgramError,
pubkey::Pubkey,
system_instruction,
sysvar::Sysvar,
};
// 定义转账指令的参数结构
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct TransferArgs {
pub amount: u64,
}
// 定义更新黑白名单的参数结构
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct UpdateListArgs {
pub account: Pubkey,
pub is_blacklist: bool,
}
// 定义账户元数据
#[derive(Accounts)]
pub struct TransferContext<'info> {
#[account(mut)]
pub from: AccountInfo<'info>,
#[account(mut)]
pub to: AccountInfo<'info>,
pub system_program: AccountInfo<'info>,
pub admin: AccountInfo<'info>, // 新增管理员账户
}
impl<'info> TransferContext<'info> {
pub fn try_from_iter(accounts_iter: &mut std::iter::Peekable<std::slice::Iter<'info, AccountInfo<'info>>>) -> ProgramResult {
let from = accounts_iter.next().ok_or(ProgramError::MissingAccount)?;
let to = accounts_iter.next().ok_or(ProgramError::MissingAccount)?;
let system_program = accounts_iter.next().ok_or(ProgramError::MissingAccount)?;
let admin = accounts_iter.next().ok_or(ProgramError::MissingAccount)?;
Ok(TransferContext {
from: from.clone(),
to: to.clone(),
system_program: system_program.clone(),
admin: admin.clone(),
})
}
}
/// 转账指令处理器
pub fn process_transfer(
program_id: &Pubkey,
accounts: TransferContext,
transfer_args: TransferArgs,
) -> ProgramResult {
let TransferContext {
from,
to,
system_program,
admin,
} = accounts;
// 检查转账金额是否大于0
if transfer_args.amount == 0 {
msg!("Amount must be greater than zero");
return Err(ProgramError::InvalidArgument);
}
// 检查黑名单
if is_blacklisted(from.key, admin) {
msg!("Account is blacklisted");
return Err(ProgramError::InvalidAccountData);
}
// 检查白名单
if !is_whitelisted(to.key, admin) {
msg!("Recipient is not whitelisted");
return Err(ProgramError::InvalidAccountData);
}
// 执行转账
invoke(
&system_instruction::transfer(from.key, to.key, transfer_args.amount),
&[from.clone(), to.clone(), system_program.clone()],
)?;
Ok(())
}
/// 设置管理员指令处理器
pub fn process_set_admin(
program_id: &Pubkey,
accounts: TransferContext,
admin_key: Pubkey,
) -> ProgramResult {
let TransferContext {
admin,
..
} = accounts;
// 检查是否为当前管理员账户
if !admin_is_admin(admin.key) {
msg!("Not an admin account");
return Err(ProgramError::InvalidAccountData);
}
// 更新管理员账户
update_admin(admin_key);
Ok(())
}
/// 添加到黑名单指令处理器
pub fn process_add_to_blacklist(
program_id: &Pubkey,
accounts: TransferContext,
account_key: Pubkey,
) -> ProgramResult {
let TransferContext {
admin,
..
} = accounts;
// 检查是否为管理员账户
if !admin_is_admin(admin.key) {
msg!("Not an admin account");
return Err(ProgramError::InvalidAccountData);
}
// 添加账户到黑名单
add_to_blacklist(account_key);
Ok(())
}
/// 添加到白名单指令处理器
pub fn process_add_to_whitelist(
program_id: &Pubkey,
accounts: TransferContext,
account_key: Pubkey,
) -> ProgramResult {
let TransferContext {
admin,
..
} = accounts;
// 检查是否为管理员账户
if !admin_is_admin(admin.key) {
msg!("Not an admin account");
return Err(ProgramError::InvalidAccountData);
}
// 添加账户到白名单
add_to_whitelist(account_key);
Ok(())
}
/// 检查账户是否在黑名单中
fn is_blacklisted(account: &Pubkey, admin: &AccountInfo) -> bool {
// 这里应该有一个真正的黑名单列表
let blacklist = get_blacklist(admin);
blacklist.contains(account)
}
/// 检查账户是否在白名单中
fn is_whitelisted(account: &Pubkey, admin: &AccountInfo) -> bool {
// 这里应该有一个真正的白名单列表
let whitelist = get_whitelist(admin);
whitelist.contains(account)
}
/// 获取黑名单列表
fn get_blacklist(admin: &AccountInfo) -> Vec<Pubkey> {
// 这里应该从管理员账户读取黑名单列表
vec![
Pubkey::new_unique(),
Pubkey::new_unique(),
]
}
/// 获取白名单列表
fn get_whitelist(admin: &AccountInfo) -> Vec<Pubkey> {
// 这里应该从管理员账户读取白名单列表
vec![
Pubkey::new_unique(),
Pubkey::new_unique(),
]
}
/// 检查是否为管理员账户
fn admin_is_admin(account: &Pubkey) -> bool {
// 这里应该有一个真正的管理员账户列表
let admins = vec![
Pubkey::new_unique(),
Pubkey::new_unique(),
];
admins.contains(account)
}
/// 更新管理员账户
fn update_admin(new_admin: Pubkey) {
// 这里应该更新管理员账户列表
}
/// 添加账户到黑名单
fn add_to_blacklist(account: Pubkey) {
// 这里应该更新黑名单列表
}
/// 添加账户到白名单
fn add_to_whitelist(account: Pubkey) {
// 这里应该更新白名单列表
}
// 入口点
solana_program::entrypoint!(process_instruction);
// 处理指令
fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
let accounts_iter = &mut accounts.iter();
match instruction_data[0] {
0 => {
let transfer_args = TransferArgs::unpack_from_slice(&instruction_data[1..])?;
let accounts = TransferContext::try_from_iter(accounts_iter)?;
process_transfer(program_id, accounts, transfer_args)
},
1 => {
let admin_key = Pubkey::new_unique();
let accounts = TransferContext::try_from_iter(accounts_iter)?;
process_set_admin(program_id, accounts, admin_key)
},
2 => {
let account_key = Pubkey::new_unique();
let accounts = TransferContext::try_from_iter(accounts_iter)?;
process_add_to_blacklist(program_id, accounts, account_key)
},
3 => {
let account_key = Pubkey::new_unique();
let accounts = TransferContext::try_from_iter(accounts_iter)?;
process_add_to_whitelist(program_id, accounts, account_key)
},
_ => Err(ProgramError::InvalidInstructionData),
}
}
步骤 4: 更新测试代码
现在我们需要更新测试代码以使用新的指令处理器。以下是更新后的测试代码:
use solana_program_test::*;
use solana_sdk::{
signature::{Keypair, Signer},
transaction::Transaction,
system_instruction,
system_program,
pubkey::Pubkey,
};
use my_program; // 替换为你的智能合约模块名称
// 测试用的账户密钥对
let alice = Keypair::new();
let bob = Keypair::new();
let admin = Keypair::new();
let blacklisted_account = Keypair::new();
let whitelisted_account = Keypair::new();
// 测试用的转账金额
const TRANSFER_AMOUNT: u64 = 100;
#[tokio::test]
async fn test_transfer() {
let mut test_context = TestContext::new().await;
let program_id = test_context.add_program("my_program", my_program::id(), None).await;
// 初始化账户
initialize_accounts(&mut test_context, &program_id).await;
// 测试正常转账
test_normal_transfer(&mut test_context, &program_id).await.unwrap();
// 测试黑名单转账
test_blacklist_transfer(&mut test_context, &program_id).await.unwrap();
// 测试白名单转账
test_whitelist_transfer(&mut test_context, &program_id).await.unwrap();
}
// 初始化账户
async fn initialize_accounts(test_context: &mut TestContext, program_id: &Pubkey) {
// 创建并初始化账户
test_context
.banks_client
.with_signer(&alice)
.send_and_confirm_transaction(Transaction::new_signed_with_payer(
&[system_instruction::create_account(
&test_context.payer.pubkey(),
&alice.pubkey(),
1_000_000, // 初始资金
1024, // 空间大小
program_id,
)],
Some(&test_context.payer.pubkey()),
&[&test_context.payer, &alice],
test_context.last_blockhash,
))
.await
.unwrap();
test_context
.banks_client
.with_signer(&bob)
.send_and_confirm_transaction(Transaction::new_signed_with_payer(
&[system_instruction::create_account(
&test_context.payer.pubkey(),
&bob.pubkey(),
1_000_000, // 初始资金
1024, // 空间大小
program_id,
)],
Some(&test_context.payer.pubkey()),
&[&test_context.payer, &bob],
test_context.last_blockhash,
))
.await
.unwrap();
test_context
.banks_client
.with_signer(&admin)
.send_and_confirm_transaction(Transaction::new_signed_with_payer(
&[system_instruction::create_account(
&test_context.payer.pubkey(),
&admin.pubkey(),
1_000_000, // 初始资金
1024, // 空间大小
program_id,
)],
Some(&test_context.payer.pubkey()),
&[&test_context.payer, &admin],
test_context.last_blockhash,
))
.await
.unwrap();
test_context
.banks_client
.with_signer(&blacklisted_account)
.send_and_confirm_transaction(Transaction::new_signed_with_payer(
&[system_instruction::create_account(
&test_context.payer.pubkey(),
&blacklisted_account.pubkey(),
1_000_000, // 初始资金
1024, // 空间大小
program_id,
)],
Some(&test_context.payer.pubkey()),
&[&test_context.payer, &blacklisted_account],
test_context.last_blockhash,
))
.await
.unwrap();
test_context
.banks_client
.with_signer(&whitelisted_account)
.send_and_confirm_transaction(Transaction::new_signed_with_payer(
&[system_instruction::create_account(
&test_context.payer.pubkey(),
&whitelisted_account.pubkey(),
1_000_000, // 初始资金
1024, // 空间大小
program_id,
)],
Some(&test_context.payer.pubkey()),
&[&test_context.payer, &whitelisted_account],
test_context.last_blockhash,
))
.await
.unwrap();
}
// 测试正常转账
async fn test_normal_transfer(test_context: &mut TestContext, program_id: &Pubkey) -> Result<(), ProgramError> {
let transfer_args = TransferArgs { amount: TRANSFER_AMOUNT };
let accounts = TransferContext::try_from_iter(&[
&alice.pubkey(),
&bob.pubkey(),
&system_program::id(),
&admin.pubkey(),
]).unwrap();
test_context
.banks_client
.with_signer(&admin)
.send_and_confirm_transaction(Transaction::new_signed_with_payer(
&[solana_program::instruction::Instruction::new_with_borsh(
*program_id,
0, // 指令类型
vec![accounts.from.clone(), accounts.to.clone(), accounts.system_program.clone(), accounts.admin.clone()],
transfer_args,
)],
Some(&admin.pubkey()),
&[&admin],
test_context.last_blockhash,
))
.await?;
Ok(())
}
// 测试黑名单转账
async fn test_blacklist_transfer(test_context: &mut TestContext, program_id: &Pubkey) -> Result<(), ProgramError> {
// 将账户添加到黑名单
let accounts = TransferContext::try_from_iter(&[
&admin.pubkey(),
&blacklisted_account.pubkey(),
&system_program::id(),
&admin.pubkey(),
]).unwrap();
test_context
.banks_client
.with_signer(&admin)
.send_and_confirm_transaction(Transaction::new_signed_with_payer(
&[solana_program::instruction::Instruction::new_with_borsh(
*program_id,
2, // 指令类型
vec![accounts.from.clone(), accounts.to.clone(), accounts.system_program.clone(), accounts.admin.clone()],
UpdateListArgs { account: blacklisted_account.pubkey(), is_blacklist: true },
)],
Some(&admin.pubkey()),
&[&admin],
test_context.last_blockhash,
))
.await?;
// 尝试从黑名单账户转账
let transfer_args = TransferArgs { amount: TRANSFER_AMOUNT };
let accounts = TransferContext::try_from_iter(&[
&blacklisted_account.pubkey(),
&bob.pubkey(),
&system_program::id(),
&admin.pubkey(),
]).unwrap();
assert!(test_context
.banks_client
.with_signer(&admin)
.send_and_confirm_transaction(Transaction::new_signed_with_payer(
&[solana_program::instruction::Instruction::new_with_borsh(
*program_id,
0, // 指令类型
vec![accounts.from.clone(), accounts.to.clone(), accounts.system_program.clone(), accounts.admin.clone()],
transfer_args,
)],
Some(&admin.pubkey()),
&[&admin],
test_context.last_blockhash,
))
.await
.is_err());
Ok(())
}
// 测试白名单转账
async fn test_whitelist_transfer(test_context: &mut TestContext, program_id: &Pubkey) -> Result<(), ProgramError> {
// 将账户添加到白名单
let accounts = TransferContext::try_from_iter(&[
&admin.pubkey(),
&whitelisted_account.pubkey(),
&system_program::id(),
&admin.pubkey(),
]).unwrap();
test_context
.banks_client
.with_signer(&admin)
.send_and_confirm_transaction(Transaction::new_signed_with_payer(
&[solana_program::instruction::Instruction::new_with_borsh(
*program_id,
3, // 指令类型
vec![accounts.from.clone(), accounts.to.clone(), accounts.system_program.clone(), accounts.admin.clone()],
UpdateListArgs { account: whitelisted_account.pubkey(), is_blacklist: false },
)],
Some(&admin.pubkey()),
&[&admin],
test_context.last_blockhash,
))
.await?;
// 尝试向白名单账户转账
let transfer_args = TransferArgs { amount: TRANSFER_AMOUNT };
let accounts = TransferContext::try_from_iter(&[
&alice.pubkey(),
&whitelisted_account.pubkey(),
&system_program::id(),
&admin.pubkey(),
]).unwrap();
test_context
.banks_client
.with_signer(&admin)
.send_and_confirm_transaction(Transaction::new_signed_with_payer(
&[solana_program::instruction::Instruction::new_with_borsh(
*program_id,
0, // 指令类型
vec![accounts.from.clone(), accounts.to.clone(), accounts.system_program.clone(), accounts.admin.clone()],
transfer_args,
)],
Some(&admin.pubkey()),
&[&admin],
test_context.last_blockhash,
))
.await?;
Ok(())
}
总结
以上步骤完成了智能合约的更新,包括添加了新的指令处理器以支持设置管理员、添加到黑名单和白名单等功能,并更新了测试代码来验证这些功能。请注意,上述代码示例中的 get_blacklist, get_whitelist, admin_is_admin, update_admin, add_to_blacklist 和 add_to_whitelist 函数都需要根据实际情况实现,例如通过维护一个账户状态来存储这些列表。