Solana程序第一部分:理解SPL代币铸造

  • Sec3dev
  • 发布于 2022-03-22 21:29
  • 阅读 41

本文深入探讨Solana的SPL Token Mint,详细介绍了其常用指令和数据结构,包括如何初始化mint、token账户,铸造、燃烧和转移代币等。通过逐步分析每个指令的实现和逻辑,读者能够全面理解SPL token的工作原理和应用场景。

Solana 代币程序( 源代码)是最常执行的 Solana 智能合约之一。其程序 ID: TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA

  • 大多数用户部署的 Solana 智能合约(直接或间接)使用代币程序来铸造/转移/销毁代币(即,SPL 代币)。例如,如果你的 Solana 项目是一个去中心化交易所、稳定币、ICO 或跨链桥,你很可能依赖于代币程序。
  • SPL 代币类似于 ERC20/ERC721 代币,但有一些复杂的差异。

在本文中,我们详述 SPL 代币并介绍在代币程序中最常用的指令的内部实现:

指令 SPL 功能
InitializeMint spl_token::instruction::initialize_mint
InitializeAccount spl_token::instruction::initialize_account
MintTo spl_token::instruction::mint_to
Burn spl_token::instruction::burn
Transfer spl_token::instruction::transfer
SyncNative spl_token::instruction::sync_native
Approve spl_token::instruction::approve
Revoke spl_token::instruction::revoke
FreezeAccount spl_token::instruction::freeze_account
ThawAccount spl_token::instruction::thaw_account
CloseAccount spl_token::instruction::close_account
SetAuthority spl_token::instruction::set_authority

这些指令的实现包含三个重要的数据结构:

  • Mint — 每个铸造账户的数据类型(例如,mint_info.data
  • Account — 每个代币账户的数据类型(例如,source_account_info.data
  • Multisig — 每个多重签名账户的数据类型(例如,owner_account_info.data

InitializeMint 指令

InitializeMint 指令是 SPL 代币铸造过程中第一个调用的指令。它将调用函数 _process_initialize_mint 来初始化铸造账户(在第 36 行中,accounts 向量中的第一个账户提供的为 mint_info):

1*gvZGpBVIKR_9xkwfLrxVQQ.png (700×197)

铸造账户的数据被反序列化为 mintMint 数据类型,然后检查重新初始化错误:

1*JsFIMaSe7d9Ue-Ykif3z7Q.png (700×56)

Mint 结构有五个属性:

1*u1g1F3JOJQq7C69k6Mob2A.png (700×220)

最后,通过设置其 mint_authoritydecimals、将 is_initialized 标志设置为 true,以及一个可选的 freeze_authority 来初始化铸造账户。

1*2XYXi9b_3mL0-mI45ZfHJg.png (700×103)

注意,在上述初始化代码中,铸造的 supply 没有设置,但默认为 0

InitializeAccount 指令

该指令将调用函数 _process_initialize_account 来初始化一个代币账户(在第 90 行,new_account_info 提供为 accounts 向量中的第一个账户):

1*I_gLVjqLn-hZptn7824v5A.png (700×262)

InitializeMint 类似,代币账户的数据被反序列化为 accountToken 数据类型,并检查重新初始化错误:

1*7dvGosCsm_-spny1qcIoLg.png (700×49)

Token 结构具有八个属性:

1*iW2ZKPRMaJkINv6NGwQScQ.png (700×330)

最后,通过设置其 mintownerclose_authority(默认为 None)、delegate(默认为 None)、delegated_amount(默认为 0)、state(已初始化)、is_native 标志和 amount(即该账户所持有的代币数量)来初始化代币账户:

1*GwF77C2Yr0fLTqlL1_M4Rw.png (700×288)

1*TLFhhnd3OPQk9DU4TpjvZA.png (700×17)

注意,当代币铸造为本地铸造时,即 WSOL(包装的 SOL,程序 ID: So11111111111111111111111111111111111111112),is_native 为 true,amount 被初始化为代币账户中的 lamports 数量(减去租金)。

MintTo 指令

在铸造和代币账户被初始化后,可以通过调用 MintTo 指令来铸造代币,触发函数 process_mint_to

1*isw0CQS2vxbWXMR741QS6Q.png (700×184)

MintTo 指令接受三个用户提供的账户:

  • mint_info: 铸造账户(第 523 行)
  • destination_account_info : 接收铸造代币的代币账户(第 524 行)
  • owner_info : 铸造账户的权限(第 525 行)

它还接受两个用户提供的指令数据:

  • amount : 要铸造的代币数量
  • expected_decimals : 期望的铸造小数点(用于铸造验证)

注意,对于 MintTo 指令有几个有效性检查:

  1. 目标账户未被冻结,且不是本地账户
  2. 目标账户的铸造是有效的:这是铸造账户的密钥
  3. 目标账户的所有者和铸造账户的所有者都是代币程序
  4. 交易由铸造账户的权限签名

1*sw3vJvjG1uGyENhSlAxRfA.png (700×151)

最后,如果处理 MintTo 指令成功,目标账户的代币金额( destination_account.amount)和铸造的供应量( mint.supply)将都增加 amount

1*UROtwqzY7Co-dAXmx8IIpg.png (700×224)

Burn 指令

Burn 指令(大多数情况下)与 MintTo 相对。它调用函数 process_burn 从源代币账户销毁一定数量的代币:

1*tQs5QTrw--lfNQte8Imjug.png (700×214)

如果 Burn 指令成功,源账户的代币数量( source_account.amount)和铸造的供应量( mint.supply)都将减少 amount

1*gMpCkjZOpK_VSRrWvnCLZw.png (700×232)

一个主要的区别是,签名的权限账户不是铸造的权限,而是源账户的所有者或委托者。对于后者,逻辑稍显复杂:

  1. 检查源账户的委托者是否与用户提供的权限账户匹配(第 617 行),并且该账户已被签名(第 618–623 行)。
  2. 检查被销毁的 amount 是否不超过源账户的 delegate_amount(第 625 行)
  3. delegate_amount 减少被销毁的 amount(第 628–631 行),如果 delegate_amount 变为零,则将源账户的 delegate 设置为 None(第 632–633 行)。

1*R8Ei8-S-MoAnKVT_-BUxmg.png (700×261)

Transfer 指令

Transfer 指令将调用函数 process_transfer 将一定数量的代币从源账户转移到目标账户:

1*Ntyf-m894FCOWo6lgTOMBQ.png (700×174)

1*6l8GYN7GNnm6mvPnIyMOYg.png (700×37)

该函数有几个重要的有效性检查:

  1. source_accountdestination_account 均未被冻结
  2. source_account 的铸造和 destination_account 的铸造相同
  3. 转移的 amount 不超过 source_account 的代币数量
  4. 权限(无论是 source_account 的所有者或与之匹配的委托者)已被签名

注意: source_account 可能与 destination_account 相同。如果是这样,则为自我转移,函数将简单地返回 Ok。换句话说,自我转移是代币程序允许的

最后,如果 Transfer 指令成功,source_account 的代币数量减少 amount,而 destination_account 的代币数量增加 amount

1*nlE29LMTjAtn0luonfeq8Q.png (700×196)

注意: 对于原生代币账户(即,代币铸造为 WSOL),为了能够通过代币程序直接转移 SOL,代币金额应“同步”到账户中 lamports 的数量。

因此,source_accountdestination_account 的 lamports 也必须相应更新:

1*PrZYWIFdx1pW3QeAxd2cxQ.png (700×171)

SyncNative 指令

对于原生代币账户,当代币金额小于账户中的 lamports 数量(可能由于将 lamports system_instruction 转移到代币账户造成),代币程序有 SyncNative 指令来同步这两个值:

1*TTU5f1Mvtm-4yD7-TJfMzA.png (700×292)

在上面的代码中,本地账户的数量更新为账户中 lamports 的数量减去租金( 第 768 行)。

Approve 指令

Approve 指令将调用函数 process_approve 为源账户设置一个委托账户及其委托金额,权限由源账户的权限提供:

1*0odfrTCp2G1RePGq_UYjOw.png (700×172)

1*VuUrr3TYbCP6ljXG6e3_zg.png (700×47)

注意,代币账户一次只允许一个委托。

Approve 指令将覆盖之前委托的设置和上一个 Approve 指令设置的委托金额。即使之前的委托金额没有被花费或只部分被花费,它仍将被覆盖。

Revoke 指令

Revoke 指令将调用函数 process_revoke 将源账户的委托设置为 None,并将委托金额设置为 0

1*8CaZ1fotX_kwSkkMuPyz1g.png (700×117)

1*sfxB_qfcF_z94O1yaRZGLw.png (700×69)

FreezeAccount 和 ThawAccount 指令

FreezeAccountThawAccount 指令将调用函数 process_toggle_freeze_account 来分别将源账户的状态设置为 FrozenInitialized

1*alhFU5U4AQqQq_PsHWtZvg.png (700×174)

1*qgLEYsIzD2SsjMT7Q2UMWw.png (700×151)

注意,该指令必须由铸造账户的 freeze_authority 签名(第 716 行 mint_info)。如果铸造的 free_authority 未设置,则将失败。

CloseAccount 指令

CloseAccount 指令将调用函数 process_close_account 来关闭一个代币账户( source_account),并通过源账户的权限将其所有 lamports 转移到另一个代币账户( destination_account):

1*6rc92OABks4Uu0DaKQL3sQ.png (700×85)

关闭代币账户包括三个步骤:

  1. 将其所有 lamports 转移到目标账户(第 695-697 行)
  2. 将其 lamports 设置为零(第 700 行)
  3. 调用 sol_memset 来清除账户的数据(第 702 行)

1*0jl3el6XSTMj3MVQuXjh3A.png (700×142)

注意,源账户必须与目标账户不同:

1*CJKPWxY1g8M5s4wycOtfvA.png (700×34)

SetAuthority 指令

SetAuthority 指令用于为铸造或代币账户设置新的权限。权限可以是单个账户(钱包或 PDA)或多重签名账户(稍后将详细说明)。

1*VZkuCKWwKLFBubCHiwvyvA.png (700×183)

要设置的权限类型由 authority_type 指定。共有四种不同类型:

  1. AuthorityType::AccountOwner : 设置代币账户的所有者
  2. AuthorityType::CloseAccount : 设置代币账户的 close_authority
  3. AuthorityType::MintTokens : 设置铸造账户的 mint_authority
  4. AuthorityType::FreezeAccount : 设置铸造账户的 free_authority

Multisig 结构

Multisig 账户有 Multisig 数据类型及四个属性:

  • m — 所需签名者的数量
  • n — 有效签名者的总数
  • is_initialized — 初始化标志
  • signers — 所有有效签名者公钥的数组

1*A6u_24OeJqWWeiWDyAMJ3g.png (700×252)

Multisig 账户可以通过 InitializeMultisig 指令初始化(第 175 行中的 multisig_info 账户):

1*cZ4HwSTyVBTZzEfwt8ZThA.png (700×146)

在初始化时,m 作为参数传入,n 为有效签名账户的长度(第 192 行,signer_infos 提供为 accounts 向量):

1*V-AZRK0IzMIvhy_bTgG2pw.png (700×249)

我们将继续在下一些文章中介绍其他流行的 Solana 程序的技术细节(例如,token-swapstake-poolassociated-token-account)。


关于 sec3(前身 Soteria)

sec3 是一家安全研究公司,致力于为数百万用户准备 Solana 项目。sec3 的 Launch Audit 是一项严格的、由研究人员主导的代码检查,调查并认证主网级别的智能合约;sec3 的持续审计软件平台 X-ray 与 GitHub 集成,渐进式扫描拉取请求,帮助项目在部署前加强代码;sec3 的部署后安全解决方案 WatchTower 确保资金安全。sec3 正在为 Web3 项目构建基于技术的可扩展解决方案,以确保协议在规模扩大时保持安全。

要了解更多关于 sec3 的信息,请访问 https://www.sec3.dev

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

0 条评论

请先 登录 后评论
Sec3dev
Sec3dev
https://www.sec3.dev/