当前位置: 首页 > news >正文

anchor 智能合约案例5 之 vesting

前言:

前面我们介绍了 solana 关于 anchor 智能合约的环境搭建 配置,简单编写 发布 部署 调用 的一些落地。等等。接下来我们借着案例。详细剖析下智能合约编写。

 案例 vesting 介绍:

这是一个关于 释放 spl  token 的案例。老板将 token 作为激励 发放给员工。包含 锁定期, 受益人 开始兑换时间,结束兑换时间, 解锁账户 等等,

主要功能步骤

  1. 雇主先创建一个  以公司名称作为派生种子的账户,用于存放 待释放 SPL Token,
  2. 雇主再为 每个员工创建一个 pad 账户,记录员工待解锁 账户的信息,(token总量,锁定期限,开始兑换期限,员工接收token地址 等等, )
  3. 员工领取 token

智能合约的分析: 

智能合约 编写包含很多固定写法。这里就不赘述了。可以先看前面的章节内容。我们这里主要介绍下 学到的新姿势:

 

  • 雇主创建 账户
    /**
    创建 老板解锁 账户:主要就是 给 vesting_account 赋值
    **/
    pub fn create_vesting_account(ctx: Context<CreateVestingAccount>,company_name:String
    ) -> Result<()> {*ctx.accounts.vesting_account = VestingAccount {owner: ctx.accounts.signer.key(),mint: ctx.accounts.mint.key(),treasury_token_account: ctx.accounts.treasury_token_account.key(),company_name,treasury_bump: ctx.bumps.treasury_token_account,bump: ctx.bumps.treasury_token_account,};Ok(())
    }#[derive(Accounts)]
    #[instruction(company_name:String)]
    pub struct CreateVestingAccount<'info>{#[account(mut)]pub signer: Signer<'info>,#[account(init,space = 8 + VestingAccount::INIT_SPACE,payer = signer,seeds = [company_name.as_ref()],bump,)]pub vesting_account: Account<'info, VestingAccount>,pub mint:InterfaceAccount<'info, Mint>,/**1. 需要创建2. 指定 spl  mint3. 授权账户就是自己4. 支付的费用5. 生成 pda 账户种子6. pda bump**/#[account(init,token::mint = mint,token::authority = treasury_token_account,payer = signer,seeds = [b"vesting_treasury".as_ref(),company_name.as_ref()],bump,)]pub treasury_token_account:InterfaceAccount<'info,TokenAccount>,pub system_program: Program<'info, System>,pub token_program: Interface<'info, TokenInterface>,
    }/**
    解锁账户 雇主操作的账户 用于公司员工管理,存储需要解锁的 spl token1.owner 归属人 觉得谁可以更新2.spl 发放 spl mint3.用于保存 spl 账户信息,雇主 用于分配 所有要发放的 SPL4.公司名称 标识,  用于派生 PDA5.保存 分配 SPL 账户的 bump,6.保存 归属账号的 bump宏 :#[account]  用于构造数据 指定改结构体为一个 solana 账户#[derive(InitSpace)] 是对数据结构的实现, 指定我们想要使用 InitSpace,会让 anchor 自动计算 结构体大小#[max_len(50)]  字符串类型没有固定大小,这里指定为 固定大小 才能使用 InitSpace 推导出结构体大小
    **/
    #[account]
    #[derive(InitSpace)]
    pub struct VestingAccount {pub owner: Pubkey,pub mint: Pubkey,pub treasury_token_account: Pubkey,#[max_len(50)]pub company_name:String,pub treasury_bump:u8,pub bump:u8,
    }
  • 雇主为每个员工 创建账号

    /**
    创建雇员 待解锁的账号:
    结构体包含 解锁 token 的  所有信息
    **/
    pub fn create_employee_account(ctx: Context<CreateEmployeeAccount>,start_time:i64,end_time:i64,total_amount:u64,cliff_time:i64,
    ) -> Result<()> {*ctx.accounts.employee_account = EmployeeAccount {beneficiary: ctx.accounts.beneficiary.key(),start_time,end_time,total_amount,total_withdrawn:0,cliff_time,vesting_account: ctx.accounts.vesting_account.key(),bump:ctx.bumps.employee_account,};Ok(())
    }/**1.支付费用的账户2.受益人账户,领取 token 的 账户地址3.操作该账户的权限4.员工账户5.指定系统程序
    **/
    #[derive(Accounts)]
    pub struct CreateEmployeeAccount<'info>{#[account(mut)]pub owner:Signer<'info>,pub beneficiary:SystemAccount<'info>,//has_one 解锁 账户的所有者 是刺指令的签名者 owner#[account(has_one = owner,)]pub vesting_account:Account<'info, VestingAccount>,//创建员工账户#[account(init,space = 8 + EmployeeAccount::INIT_SPACE,payer = owner,seeds = [b"employee_vesting", beneficiary.key().as_ref(), vesting_account.key().as_ref()],bump,)]pub employee_account: Account<'info, EmployeeAccount>,pub system_program: Program<'info, System>,
    }/**1.受益人  接收token 的雇员 钱包 (ATA账号)2.开始兑换时间  unix 时间3.截止的兑换时间4.锁定期 员工需要等待多久才能解锁(类似期权,几年不能交易)5.解锁账户 雇主创建的放 spl token 的账户6.分配给该员工的 总 spl 数量7.记录该员工 解锁的数量,用于后期计算
    **/
    #[account]
    #[derive(InitSpace)]
    pub struct EmployeeAccount {pub beneficiary: Pubkey,pub start_time:i64,pub end_time:i64,pub cliff_time:i64,pub vesting_account: Pubkey,pub total_amount:u64,pub total_withdrawn:u64,pub bump:u8,
    }
  • 员工释放 token

    /**
    员工认领 Token,保证员工在正确的时间里 领取 token**/
    pub fn claim_tokens(ctx:Context<ClaimTokens>,company_name:String,) -> Result<()>{//获取员工账户(有老板已经创建好的)let employee_account =&mut ctx.accounts.employee_account;//获取系统时间let now = Clock::get()?.unix_timestamp;//当前时间小于 锁定期则 不能释放if now < employee_account.cliff_time {return  Err(ErrorCode::ClaimNotAvailableYet.into())}//已经开放的时间, saturating_sub 防止数组越界let time_since_start = now.saturating_sub(employee_account.start_time);//总释放时间let total_vesting_time = employee_account.end_time.saturating_sub(employee_account.start_time);if total_vesting_time == 0 {return  Err(ErrorCode::InvalidVestingPeriod.into())}//解锁数量//如果当前时间 大于 释放结束时间。那可以全部都 解锁掉let vested_amount = if now >= employee_account.end_time {employee_account.total_amount}else {//checked_mul 防止 乘法的溢出//数量*总的开放时间 / 当前已经开放的时间 = 当前开放时间可以获取的数量match employee_account.total_amount.checked_mul(time_since_start as u64) {Some(product) => product / (total_vesting_time as u64),None =>{return  Err(ErrorCode::CalculationOverflow.into())}}};//可申领金额let claimable_amount = vested_amount.saturating_sub(employee_account.total_withdrawn);if claimable_amount == 0 {return  Err(ErrorCode::NothingToClaim.into())}//从老板设立的账号中 转账 到员工let transfer_cpi_accounts = TransferChecked {from: ctx.accounts.treasury_token_account.to_account_info(),mint: ctx.accounts.mint.to_account_info(),//to: ctx.accounts.employee_account.to_account_info(),to: employee_account.to_account_info(),//已经将权限设置为自己了。所有这里是可以调用的authority: ctx.accounts.treasury_token_account.to_account_info(),};let cip_program = ctx.accounts.token_program.to_account_info();let signer_seeds:&[&[&[u8]]] = &[&[b"vesting_treasury",ctx.accounts.vesting_account.company_name.as_ref(),&[ctx.accounts.vesting_account.treasury_bump],]];let cpi_context = CpiContext::new(cip_program, transfer_cpi_accounts).with_signer(signer_seeds);let decimals = ctx.accounts.mint.decimals;token_interface::transfer_checked(cpi_context, claimable_amount, decimals)?;employee_account.total_withdrawn += claimable_amount;Ok(())
    }}

依赖 Cargo.toml:

[package]
name = "tokenvesting"
version = "0.1.0"
description = "Created with Anchor"
edition = "2021"[lib]
crate-type = ["cdylib", "lib"]
name = "tokenvesting"[features]
default = []
cpi = ["no-entrypoint"]
no-entrypoint = []
no-idl = []
no-log-ix-name = []
idl-build = ["anchor-lang/idl-build","anchor-spl/idl-build"][dependencies]
#cargo add anchor-lang --features init-if-needed
anchor-lang = { version = "0.31.1", features = ["init-if-needed"] }
anchor-spl = "0.31.1"
solana-program = "2.3.0"

完整脚本: 

#![allow(clippy::result_large_err)]use anchor_lang::prelude::*;
use anchor_spl::associated_token::AssociatedToken;
use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface, TransferChecked};declare_id!("JAVuBXeBZqXNtS73azhBDAoYaaAFfo4gWXoZe2e7Jf8H");/**
(智能合约 和 http 请求类似都是 无状态的)
智能合约主要功能:1.雇主先创建一个  以公司名称作为派生种子的账户,用于存放 待释放 SPL Token,2.雇主再为 每个员工创建一个 pad 庄户,记录员工带解锁庄户的信息,(token总量,锁定期限,开始兑换期限,员工接收token地址 等等, )3.员工领取 token**/
#[program]
pub mod vesting {use super::*;use anchor_spl::token_interface;/**创建 老板解锁 账户:主要就是 给 vesting_account 赋值**/pub fn create_vesting_account(ctx: Context<CreateVestingAccount>,company_name: String,) -> Result<()> {*ctx.accounts.vesting_account = VestingAccount {owner: ctx.accounts.signer.key(),mint: ctx.accounts.mint.key(),treasury_token_account: ctx.accounts.treasury_token_account.key(),company_name,treasury_bump: ctx.bumps.treasury_token_account,bump: ctx.bumps.treasury_token_account,};Ok(())}/**创建雇员 待解锁的账号:结构体包含 解锁 token 的  所有信息**/pub fn create_employee_account(ctx: Context<CreateEmployeeAccount>,start_time: i64,end_time: i64,total_amount: u64,cliff_time: i64,) -> Result<()> {*ctx.accounts.employee_account = EmployeeAccount {beneficiary: ctx.accounts.beneficiary.key(),start_time,end_time,total_amount,total_withdrawn: 0,cliff_time,vesting_account: ctx.accounts.vesting_account.key(),bump: ctx.bumps.employee_account,};Ok(())}/**员工认领 Token,保证员工在正确的时间里 领取 token**/pub fn claim_tokens(ctx: Context<ClaimTokens>, company_name: String) -> Result<()> {//获取员工账户(有老板已经创建好的)let employee_account = &mut ctx.accounts.employee_account;//获取系统时间let now = Clock::get()?.unix_timestamp;//当前时间小于 锁定期则 不能释放if now < employee_account.cliff_time {return Err(ErrorCode::ClaimNotAvailableYet.into());}//已经开放的时间, saturating_sub 防止数组越界let time_since_start = now.saturating_sub(employee_account.start_time);//总释放时间let total_vesting_time = employee_account.end_time.saturating_sub(employee_account.start_time);if total_vesting_time == 0 {return Err(ErrorCode::InvalidVestingPeriod.into());}//解锁数量//如果当前时间 大于 释放结束时间。那可以全部都 解锁掉let vested_amount = if now >= employee_account.end_time {employee_account.total_amount} else {//checked_mul 防止 乘法的溢出//数量*总的开放时间 / 当前已经开放的时间 = 当前开放时间可以获取的数量match employee_account.total_amount.checked_mul(time_since_start as u64){Some(product) => product / (total_vesting_time as u64),None => return Err(ErrorCode::CalculationOverflow.into()),}};//可申领金额let claimable_amount = vested_amount.saturating_sub(employee_account.total_withdrawn);if claimable_amount == 0 {return Err(ErrorCode::NothingToClaim.into());}//从老板设立的账号中 转账 到员工let transfer_cpi_accounts = TransferChecked {from: ctx.accounts.treasury_token_account.to_account_info(),mint: ctx.accounts.mint.to_account_info(),//to: ctx.accounts.employee_account.to_account_info(),to: employee_account.to_account_info(),//已经将权限设置为自己了。所有这里是可以调用的authority: ctx.accounts.treasury_token_account.to_account_info(),};let cip_program = ctx.accounts.token_program.to_account_info();let signer_seeds: &[&[&[u8]]] = &[&[b"vesting_treasury",ctx.accounts.vesting_account.company_name.as_ref(),&[ctx.accounts.vesting_account.treasury_bump],]];let cpi_context =CpiContext::new(cip_program, transfer_cpi_accounts).with_signer(signer_seeds);let decimals = ctx.accounts.mint.decimals;token_interface::transfer_checked(cpi_context, claimable_amount, decimals)?;employee_account.total_withdrawn += claimable_amount;Ok(())}
}/**
创建雇主账号1.signer  mut 签名者,支付手续费,应为需要支付手续费 所以需要是 mut2.指定 解锁账号 派生信息3.spl mint 地址4.存储 激励 spl 的 ATA 地址,(即将分配给 员工的 Token)5.指定使用到的系统程序 System6.指定使用到的系统程序 TokenInterface宏:#[instruction(company_name:String)] 是对数据结构的实现 (创建), 指定入参 company_name**/#[derive(Accounts)]
#[instruction(company_name:String)]
pub struct CreateVestingAccount<'info> {#[account(mut)]pub signer: Signer<'info>,#[account(init,space = 8 + VestingAccount::INIT_SPACE,payer = signer,seeds = [company_name.as_ref()],bump,)]pub vesting_account: Account<'info, VestingAccount>,pub mint: InterfaceAccount<'info, Mint>,/**1. 需要创建2. 指定 spl  mint3. 授权账户就是自己4. 支付的费用5. 生成 pda 账户种子6. pda bump**/#[account(init,token::mint = mint,token::authority = treasury_token_account,payer = signer,seeds = [b"vesting_treasury".as_ref(),company_name.as_ref()],bump,)]pub treasury_token_account: InterfaceAccount<'info, TokenAccount>,pub system_program: Program<'info, System>,pub token_program: Interface<'info, TokenInterface>,
}/**
解锁账户 雇主操作的账户 用于公司员工管理,存储需要解锁的 spl token1.owner 归属人 觉得谁可以更新2.spl 发放 spl mint3.用于保存 spl 账户信息,雇主 用于分配 所有要发放的 SPL4.公司名称 标识,  用于派生 PDA5.保存 分配 SPL 账户的 bump,6.保存 归属账号的 bump宏 :#[account]  用于构造数据 指定改结构体为一个 solana 账户#[derive(InitSpace)] 是对数据结构的实现, 指定我们想要使用 InitSpace,会让 anchor 自动计算 结构体大小#[max_len(50)]  字符串类型没有固定大小,这里指定为 固定大小 才能使用 InitSpace 推导出结构体大小
**/
#[account]
#[derive(InitSpace)]
pub struct VestingAccount {pub owner: Pubkey,pub mint: Pubkey,pub treasury_token_account: Pubkey,#[max_len(50)]pub company_name: String,pub treasury_bump: u8,pub bump: u8,
}/**1.支付费用的账户2.受益人账户,领取 token 的 账户地址3.操作该账户的权限4.员工账户5.指定系统程序
**/
#[derive(Accounts)]
pub struct CreateEmployeeAccount<'info> {#[account(mut)]pub owner: Signer<'info>,pub beneficiary: SystemAccount<'info>,//has_one 解锁 账户的所有者 是刺指令的签名者 owner#[account(has_one = owner,)]pub vesting_account: Account<'info, VestingAccount>,//创建员工账户#[account(init,space = 8 + EmployeeAccount::INIT_SPACE,payer = owner,seeds = [b"employee_vesting", beneficiary.key().as_ref(), vesting_account.key().as_ref()],bump,)]pub employee_account: Account<'info, EmployeeAccount>,pub system_program: Program<'info, System>,
}/**1.受益人  接收token 的雇员 钱包 (ATA账号)2.开始兑换时间  unix 时间3.截止的兑换时间4.锁定期 员工需要等待多久才能解锁(类似期权,几年不能交易)5.解锁账户 雇主创建的放 spl token 的账户6.分配给该员工的 总 spl 数量7.记录该员工 解锁的数量,用于后期计算
**/
#[account]
#[derive(InitSpace)]
pub struct EmployeeAccount {pub beneficiary: Pubkey,pub start_time: i64,pub end_time: i64,pub cliff_time: i64,pub vesting_account: Pubkey,pub total_amount: u64,pub total_withdrawn: u64,pub bump: u8,
}/**
员工认领 token1.交易的签名2.查找 待认领的 员工账户信息,雇主创建好的, mut 可编辑, has_one 约束验证,结构体中 EmployeeAccount 的两个字段与 当前约束值 是否一直3.查找 存放 解锁的 spl token 账户, mut 可编 has_one = treasury_token_account,has_one = mint 共同约束 vesting_account pda 账户4.spl mint 信息, 确定  vesting_account5.待 解锁的 老板庄户6.员工 待接收的 token account 账户
**/
#[derive(Accounts)]
#[instruction(company_name:String)]
pub struct ClaimTokens<'info> {#[account(mut)]pub beneficiary: Signer<'info>,#[account(mut,seeds = [b"employee_vesting",beneficiary.key().as_ref(),vesting_account.key().as_ref()],bump = employee_account.bump,has_one = beneficiary,has_one = vesting_account,)]pub employee_account: Account<'info, EmployeeAccount>,//宏  根据 传入的公司名称处理,是否存在 对应的pad#[account(mut,seeds = [company_name.as_ref()],bump = vesting_account.bump,has_one = treasury_token_account,has_one = mint,)]pub vesting_account: Account<'info, VestingAccount>,pub mint: InterfaceAccount<'info, Mint>,#[account(mut)]pub treasury_token_account: InterfaceAccount<'info, TokenAccount>,#[account(init_if_needed,payer = beneficiary,associated_token::mint = mint,associated_token::authority = beneficiary,associated_token::token_program = token_program,)]pub employee_token_account: InterfaceAccount<'info, TokenAccount>,pub token_program: Interface<'info, TokenInterface>,pub associated_token_program: Program<'info, AssociatedToken>,pub system_program: Program<'info, System>,
}#[error_code]
pub enum ErrorCode {#[msg("Claim not available yet")]ClaimNotAvailableYet,#[msg("Invalid Vesting Period")]InvalidVestingPeriod,#[msg("Calculation Overflow")]CalculationOverflow,#[msg("Nothing To Claim")]NothingToClaim,
}

http://www.dtcms.com/a/271966.html

相关文章:

  • 汽车加气站操作工历年考试真题及答案
  • CSS表达式——下篇【selenium】
  • WebSocket实战:实现实时聊天应用 - 双向通信技术详解
  • 【C++】——类和对象(上)
  • C 语言基础:操作符、进制与数据表示通俗讲解
  • AI【应用 03】Windows环境部署 TTS CosyVoice2.0 详细流程记录(Matcha-TTS、spk2info.pt等文件分享)
  • Qt中处理多个同类型对象共享槽函数应用
  • git多分支管理
  • 缺陷的生命周期(Bug Life Cycle)是什么?
  • Java 正则表达式白皮书:语法详解、工程实践与常用表达式库
  • WWDC 25 风云再起:SwiftUI 7 Charts 心法从 2D 到 3D 的华丽蜕变
  • 【HarmonyOS Next之旅】DevEco Studio使用指南(四十二) -> 动态修改编译配置
  • 全面解析 wxPython:构建原生桌面应用的 Python GUI 框架
  • 【计算机基础理论知识】C++篇(二)
  • [python] 数据拷贝浪费内存,原地修改暗藏风险:如何平衡内存使用效率与数据完整性?
  • 【SpringBoot实战系列】SpringBoot3.X 整合 MinIO 存储原生方案
  • C++类对象多态底层原理及扩展问题
  • Python-GEE遥感云大数据分析与可视化(如何建立基于云计算的森林监测预警系统)
  • Yolov模型参数对比
  • Docker的/var/lib/docker/目录占用100%的处理方法
  • 变压器初级(原边)和次级(副边)的感应电动势、电压方向如何标注?
  • 安卓应用启动崩溃的问题排查记录
  • 《Effective Python》第十三章 测试与调试——使用 Mock 测试具有复杂依赖的代码
  • 【笔记分享】集合的基数、群、环、域
  • Python毕业设计232—基于python+Django+vue的图书管理系统(源代码+数据库)
  • EXCEL_单元格中图片调整代码留存
  • 什么是Kibana
  • 【C++】第十四节—模版进阶(非类型模版参数+模板的特化+模版分离编译+模版总结)
  • 保姆级搭建harbor私有仓库与docker-ce教程与使用教程
  • 机器学习基础:从理论到实践的完整指南