在上一篇文章中,我们简要介绍了如何在Aptos网络上开发Hello World程序。从现在开始,我们将更深入地研究DeFi应用程序的开发及其安全问题。与往常一样,我们想从一些基本但重要的概念开始。在本文中,我们将重点介绍Aptos硬币(即Aptos [1]中的可替代代币),包括其开发和管理以及交互。
博士
本文将告诉您:
- 什么是阿普托斯币?
- 如何创建和管理您的硬币?
- 如何与您的硬币互动?
0x1. 关于阿普托斯硬币
作为DeFi的原子,代币(或硬币)已被广泛用于区块链生态系统。它们可以用来代表多种东西,包括电子货币、质押股份和组织管理的投票权。在某种程度上,DeFi的日常活动可以简单地视为区块链系统中的大量代币流动。
以太坊已经为代币制定了一套标准。最著名的称为 ERC20,它指定了标准ERC20代币需要遵守的接口。ERC20 是一种可替代的代币标准,同时也存在不可替代的代币标准,例如 ERC721。
与其他区块链系统类似,Aptos也有其代币标准[2],该标准定义了如何在各自的区块链上创建和使用数字资产。具体来说,在 Aptos 中,可替代代币称为硬币 [1],而非同质化代币(即 NFT)称为代币 [3]。在下文中,我们将讨论创建,管理和与Aptos硬币交互的方法。
0x2. 创建和管理您的第一枚硬币
Aptos提供了一个官方的标准模块(类似于ERC20):通过调用该模块的API,任何用户都可以轻松创建自己的硬币。此外,还提供了管理硬币的权限机制,这对于构建复杂的DeFi应用程序非常重要且有用。在下文中,我们将演示如何基于此模块创建硬币。coin.move
coin.move
如上一篇文章所述,您可以通过键入以下命令来创建项目:
aptos move init --name my_coin
然后,您需要在“源”文件夹下创建一个新的移动文件。现在让我们用以下示例代码填充它,该示例代码定义了一个名为 的模块,用于创建和管理名为 的标准硬币。bsc
BSC
module BlockSec::bsc{
use aptos_framework::coin;
use aptos_framework::event;
use aptos_framework::account;
use aptos_std::type_info;
use std::string::{utf8, String};
use std::signer;
struct BSC{}
struct CapStore has key{
mint_cap: coin::MintCapability<BSC>,
freeze_cap: coin::FreezeCapability<BSC>,
burn_cap: coin::BurnCapability<BSC>
}
struct BSCEventStore has key{
event_handle: event::EventHandle<String>,
}
fun init_module(account: &signer){
let (burn_cap, freeze_cap, mint_cap) = coin::initialize<BSC>(account, utf8(b"BSC"), utf8(b"BSC"), 6, true);
move_to(account, CapStore{mint_cap: mint_cap, freeze_cap: freeze_cap, burn_cap: burn_cap});
}
public entry fun register(account: &signer){
let address_ = signer::address_of(account);
if(!coin::is_account_registered<BSC>(address_)){
coin::register<BSC>(account);
};
if(!exists<BSCEventStore>(address_)){
move_to(account, BSCEventStore{event_handle: account::new_event_handle(account)});
};
}
fun emit_event(account: address, msg: String) acquires BSCEventStore{
event::emit_event<String>(&mut borrow_global_mut<BSCEventStore>(account).event_handle, msg);
}
public entry fun mint_coin(cap_owner: &signer, to_address: address, amount: u64) acquires CapStore, BSCEventStore{
let mint_cap = &borrow_global<CapStore>(signer::address_of(cap_owner)).mint_cap;
let mint_coin = coin::mint<BSC>(amount, mint_cap);
coin::deposit<BSC>(to_address, mint_coin);
emit_event(to_address, utf8(b"minted BSC"));
}
public entry fun burn_coin(account: &signer, amount: u64) acquires CapStore, BSCEventStore{
let owner_address = type_info::account_address(&type_info::type_of<BSC>());
let burn_cap = &borrow_global<CapStore>(owner_address).burn_cap;
let burn_coin = coin::withdraw<BSC>(account, amount);
coin::burn<BSC>(burn_coin, burn_cap);
emit_event(signer::address_of(account), utf8(b"burned BSC"));
}
public entry fun freeze_self(account: &signer) acquires CapStore, BSCEventStore{
let owner_address = type_info::account_address(&type_info::type_of<BSC>());
let freeze_cap = &borrow_global<CapStore>(owner_address).freeze_cap;
let freeze_address = signer::address_of(account);
coin::freeze_coin_store<BSC>(freeze_address, freeze_cap);
emit_event(freeze_address, utf8(b"freezed self"));
}
public entry fun emergency_freeze(cap_owner: &signer, freeze_address: address) acquires CapStore, BSCEventStore{
let owner_address = signer::address_of(cap_owner);
let freeze_cap = &borrow_global<CapStore>(owner_address).freeze_cap;
coin::freeze_coin_store<BSC>(freeze_address, freeze_cap);
emit_event(freeze_address, utf8(b"emergency freezed"));
}
public entry fun unfreeze(cap_owner: &signer, unfreeze_address: address) acquires CapStore, BSCEventStore{
let owner_address = signer::address_of(cap_owner);
let freeze_cap = &borrow_global<CapStore>(owner_address).freeze_cap;
coin::unfreeze_coin_store<BSC>(unfreeze_address, freeze_cap);
emit_event(unfreeze_address, utf8(b"unfreezed"));
}
}
0x2.1 基本设计
首先,看结构部分。总共定义了三种结构。
- 用作硬币唯一标识符的结构。因此,这枚硬币可以通过路径唯一地确定。
BSC
BlockSec::bsc::BSC
- 结构,用于存储从模块获得的某些功能。这些功能对应于某些特殊操作的权限,稍后将进行说明。
CapStore
aptos_framework::coin
- 用于记录用户事件的结构。
BSCEventStore
在这些结构之后,有一个函数,用于初始化模块,并且仅在模块发布在链上时调用一次。在此函数中,模块调用以将 注册为新硬币的唯一标识符。init_module
coin::initialize<BSC>
BlockSec::bsc::BSC
public fun initialize<CoinType>(
account: &signer,
name: string::String,
symbol: string::String,
decimals: u8,
monitor_supply: bool,
): (BurnCapability<CoinType>, FreezeCapability<CoinType>, MintCapability<CoinType>) {
initialize_internal(account, name, symbol, decimals, monitor_supply, false)
}
/// Capability required to mint coins.
struct MintCapability<phantom CoinType> has copy, store {}
/// Capability required to freeze a coin store.
struct FreezeCapability<phantom CoinType> has copy, store {}
/// Capability required to burn coins.
struct BurnCapability<phantom CoinType> has copy, store {}
注册后,所有接受 type in 的通用函数都将在此硬币上运行。此注册过程将返回三个功能,即 和 。这些功能分别是铸造硬币、冻结用户帐户和燃烧硬币所必需的。在某种程度上,这种功能结构的功能类似于密钥,用于打开特定权限的锁。如果有人有密钥,他可以获得相应的权限。在这里,我们将这些功能存储在结构中(由此模块的管理员/发布者拥有)中供以后使用。BlockSec::bsc::BSC
aptos_framework::coin
MintCapability
FreezeCapability
BurnCapability
CapStore
同时,在注册过程中,管理员地址下会存储一个结构,用于记录相关信息:CoinInfo
/// Information about a specific coin type. Stored on the creator of the coin's account.
struct CoinInfo<phantom CoinType> has key {
name: string::String,
/// Symbol of the coin, usually a shorter version of the name.
/// For example, Singapore Dollar is SGD.
symbol: string::String,
/// Number of decimals used to get its user representation.
/// For example, if `decimals` equals `2`, a balance of `505` coins should
/// be displayed to a user as `5.05` (`505 / 10 ** 2`).
decimals: u8,
/// Amount of this coin type in existence.
supply: Option<OptionalAggregator>,
}
调用函数后,您的硬币已在链上注册。但是,没有人可以使用这枚硬币,因为暂时不存在任何流通。为了使硬币可用,需要支持一些操作,包括发行,分配和销毁。这些操作需要我们在注册硬币时获得的能力。init_module
0x2.2 硬币管理
此硬币旨在遵守以下规则:
- 只有管理员(管理员)才能铸造硬币。
- 用户可以随时燃烧自己的硬币。
- 用户可以随时冻结/解冻其帐户。
因此,我们定义了五个管理函数,即、、和。前两个功能分别负责铸造硬币和烧硬币;而后三个用于冻结和解冻账户。mint_coin
burn_coin
freeze_self
emergency_freeze
unfreeze
铸造硬币
在我们的模块中,该函数用于铸造硬币。因为只有管理员才能铸造硬币,所以我们必须验证此功能中的相应功能。mint_coin
public entry fun mint_coin(cap_owner: &signer, to_address: address, amount: u64) acquires CapStore, BSCEventStore{
let mint_cap = &borrow_global<CapStore>(signer::address_of(cap_owner)).mint_cap;
let mint_coin = coin::mint<BSC>(amount, mint_cap);
coin::deposit<BSC>(to_address, mint_coin);
emit_event(to_address, utf8(b"minted BSC"));
}
此函数需要三个参数:
cap_owner
的类型为 ,即事务的发起者。&signer
to_address
表示铸造硬币将存入的地址。amount
表示正在铸造的硬币数量。
它包括三个步骤:获得铸造硬币的能力、铸造硬币和存放硬币的能力。
首先,在函数开始时,可以通过 .之后,用于确认帐户是否拥有以验证它是模块的管理员。通过这样做,我们可以保证只有管理员可以铸造硬币,而其他用户将在此步骤中失败。mint_coin
signer::address_of(cap_owner)
borrow_global<CapStore>
CapStore
其次,该函数随后将调用模块的函数来铸造硬币。mint_coin
mint
aptos_framework::coin
public fun mint<CoinType>(
amount: u64,
_cap: &MintCapability<CoinType>,
): Coin<CoinType> acquires CoinInfo {
if (amount == 0) {
return zero<CoinType>()
};
let maybe_supply = &mut borrow_global_mut<CoinInfo<CoinType>>(coin_address<CoinType>()).supply;
if (option::is_some(maybe_supply)) {
let supply = option::borrow_mut(maybe_supply);
optional_aggregator::add(supply, (amount as u128));
};
Coin<CoinType> { value: amount }
}
这是必需的。具体而言,需要将名为 的参数作为 的引用传递。然后,与“MintAbility”功能关联的权限也会相应地转移。虽然没有明确的访问控制,但验证由 Move 语言强制执行。MintCapability
_cap
MintCapability
第三,该函数将调用该函数将铸造的硬币存入指定的.mint_coin
deposit
to_address
提示#1:可以使用类似的方法验证特权帐户的访问控制。
燃烧硬币
燃烧硬币的过程与铸造硬币的过程不同。具体来说,只允许管理员调用该函数,而任何用户都可以调用该函数。为此,该功能必须暂时提升这些用户的权限,即获取功能。mint_coin
burn_coin
burn_coin
BurnCapability
public entry fun burn_coin(account: &signer, amount: u64) acquires CapStore, BSCEventStore{
let owner_address = type_info::account_address(&type_info::type_of<BSC>());
let burn_cap = &borrow_global<CapStore>(owner_address).burn_cap;
let burn_coin = coin::withdraw<BSC>(account, amount);
coin::burn<BSC>(burn_coin, burn_cap);
emit_event(signer::address_of(account), utf8(b"burned BSC"));
}
此函数需要两个参数:
account
的类型为 ,即事务的发起者。&signer
amount
表示正在燃烧的硬币数量。
它还包括三个步骤:获得燃烧硬币的能力,提取硬币和燃烧硬币。
显然,模块的功能要求调用方传入对 的引用,但此功能存储在 admin 中。因此,我们必须允许普通用户获得这种能力来燃烧他们持有的硬币。burn
aptos_framework::coin
BurnCapability
CapStore
public fun burn<CoinType>(
coin: Coin<CoinType>,
_cap: &BurnCapability<CoinType>,
) acquires CoinInfo {
let Coin { value: amount } = coin;
assert!(amount > 0, error::invalid_argument(EZERO_COIN_AMOUNT));
let maybe_supply = &mut borrow_global_mut<CoinInfo<CoinType>>(coin_address<CoinType>()).supply;
if (option::is_some(maybe_supply)) {
let supply = option::borrow_mut(maybe_supply);
optional_aggregator::sub(supply, (amount as u128));
}
}
为了实现这一目标,我们可能会使用 Move 语言提供的运算符。它用于从帐户的不可变全局存储中读取特定数据类型。通过使用此运算符,模块可以将管理员拥有的功能借给其他用户。也就是说,需要管理员地址才能获得我们想要的功能。borrow_global
但是,函数的事务发起者是用户而不是管理员。因此,无法通过以下方式获取管理地址(如函数)。幸运的是,可以通过 with 获取定义此结构的模块地址。由于模块是在管理员地址下发布的,我们可以相应地进一步获取管理员地址,最终获得功能。burn_coin
signer
mint_coin
aptos_std::type_info
BSC
BurnCapability
提示#2: 操作员可用于临时获取模块的功能。
borrow_global
获得 后,模块可以从用户那里提取指定数量的硬币,并具有该功能销毁硬币。BurnCapability
冻结和解冻硬币账户
基于上面的讨论,现在我们可以轻松地进行硬币账户的管理。具体来说,我们为用户提供了冻结其硬币帐户的功能。在这里,我们还提供了紧急冻结功能,该功能只能由管理员使用。此外,由于存在紧急冻结机制,不应允许用户自行解冻。因此,该功能还需要管理员解冻用户帐户。freeze_self
emergency_freeze
unfreeze
public entry fun freeze_self(account: &signer) acquires CapStore, BSCEventStore{
let owner_address = type_info::account_address(&type_info::type_of<BSC>());
let freeze_cap = &borrow_global<CapStore>(owner_address).freeze_cap;
let freeze_address = signer::address_of(account);
coin::freeze_coin_store<BSC>(freeze_address, freeze_cap);
emit_event(freeze_address, utf8(b"freezed self"));
}
public entry fun emergency_freeze(cap_owner: &signer, freeze_address: address) acquires CapStore, BSCEventStore{
let owner_address = signer::address_of(cap_owner);
let freeze_cap = &borrow_global<CapStore>(owner_address).freeze_cap;
coin::freeze_coin_store<BSC>(freeze_address, freeze_cap);
emit_event(freeze_address, utf8(b"emergency freezed"));
}
public entry fun unfreeze(cap_owner: &signer, unfreeze_address: address) acquires CapStore, BSCEventStore{
let owner_address = signer::address_of(cap_owner);
let freeze_cap = &borrow_global<CapStore>(owner_address).freeze_cap;
coin::unfreeze_coin_store<BSC>(unfreeze_address, freeze_cap);
emit_event(unfreeze_address, utf8(b"unfreezed"));
}
提示#3:通过函数返回功能时要小心!获得这些功能的恶意用户可能会滥用它们对硬币持有者造成损害!
0x3. 与硬币互动
在这里,我们专注于与硬币交互的方式。
0x3.1 注册
您可能已经注意到模块中存在一个函数:register
public entry fun register(account: &signer){
let address_ = signer::address_of(account);
if(!coin::is_account_registered<BSC>(address_)){
coin::register<BSC>(account);
};
if(!exists<BSCEventStore>(address_)){
move_to(account, BSCEventStore{event_handle: account::new_event_handle(account)});
};
}
此功能用于帮助用户注册硬币使用权限和事件记录器。为了使用某个硬币,该模块规定用户必须首先通过该功能明确注册使用该硬币的权利。aptos_framework::coin
aptos_framework::coin::register
public fun register<CoinType>(account: &signer) {
let account_addr = signer::address_of(account);
assert!(
!is_account_registered<CoinType>(account_addr),
error::already_exists(ECOIN_STORE_ALREADY_PUBLISHED),
);
account::register_coin<CoinType>(account_addr);
let coin_store = CoinStore<CoinType> {
coin: Coin { value: 0 },
frozen: false,
deposit_events: account::new_event_handle<DepositEvent>(account),
withdraw_events: account::new_event_handle<WithdrawEvent>(account),
};
move_to(account, coin_store);
}
用户只有在通过此功能注册此类硬币时才能正常持有此硬币。也就是说,如果您不想持有特定硬币,未经您的同意,其他人不能将该硬币存入您的帐户。注册实际上将一个结构(目标硬币类型)放入您的帐户中。此结构包含一个用于记录余额的结构。CoinStore
CoinStore
Coin
提示#4:与以太坊代币不同,如果没有用户的明确注册,就无法持有和操作Aptos硬币。
0x3.2 转让
假设您现在有一些硬币,那么您可以通过调用模块的功能来转移这些硬币。BSC
transfer
aptos_framework::coin
public entry fun transfer<CoinType>(
from: &signer,
to: address,
amount: u64,
) acquires CoinStore {
let coin = withdraw<CoinType>(from, amount);
deposit(to, coin);
}
请注意,这是模块提供的入口函数。该逻辑由两个公共函数的调用组成,即 和 。该功能需要权限,该权限用于将一定数量的资产从您的帐户中提取到硬币中。该功能可以将硬币存入硬币的任何注册帐户。此功能不需要额外的权限,会将指定的硬币存入帐户地址。最后,转移的硬币将自动与存储在目标地址结构中的硬币合并。coin
withdraw
deposit
withdraw
&signer
deposit
CoinStore
提示#5:提款后,硬币中的资产在当前函数的控制之下 。此函数可以将这些资产传递给函数, 而无需获取额外的权限。
transfer
deposit
0x3.3 拆分和合并
与以太坊代币不同,硬币的流通不能通过修改用户的余额来更新。相反,可以通过撤回模块中的结构来实现。通过这样做,用户通过将此结构传递给其他模块来实现资产流通。由于结构体只能由定义它的模块操作,因此该模块提供了一些接口来操作结构体,包括将币分成更小的单位和合并多个币种以满足不同场景的需求。Coin
coin
coin
Coin
1.该功能用于拆分硬币。它接收一个结构,提取其中的一部分资产以生成新结构,并返回新结构。extract
Coin
Coin
public fun extract<CoinType>(coin: &mut Coin<CoinType>, amount: u64): Coin<CoinType> {
assert!(coin.value >= amount, error::invalid_argument(EINSUFFICIENT_BALANCE));
coin.value = coin.value - amount;
Coin { value: amount }
}
2.该函数用于提取原始结构的全部值并将其存入新结构中。结果,原始结构的值将变为零(又名)。可以通过调用函数来销毁结构。extract_all
Coin
Coin
Coin
zero_coin
zero_coin
destroy_zero
public fun extract_all<CoinType>(coin: &mut Coin<CoinType>): Coin<CoinType> {
let total_value = coin.value;
coin.value = 0;
Coin { value: total_value }
}
public fun destroy_zero<CoinType>(zero_coin: Coin<CoinType>) {
let Coin { value } = zero_coin;
assert!(value == 0, error::invalid_argument(EDESTRUCTION_OF_NONZERO_TOKEN))
}
3.该功能用于合并硬币。您可以将两个结构(即 和 )的值合并到结构中并销毁结构。merge
Coin
source_coin
dst_coin
dst_coin
source_coin
public fun merge<CoinType>(dst_coin: &mut Coin<CoinType>, source_coin: Coin<CoinType>) {
spec {
assume dst_coin.value + source_coin.value <= MAX_U64;
};
dst_coin.value = dst_coin.value + source_coin.value;
let Coin { value: _ } = source_coin;
}
4. 该函数用于生成结构。zero
zero_coin
public fun zero<CoinType>(): Coin<CoinType> {
Coin<CoinType> {
value: 0
}
}
0x4. 测试硬币
要快速测试此硬币,您可以先使用以下命令进行部署(不要忘记在!Move.toml
$ aptos move publish --package-dir ./
Compiling, may take a little while to download git dependencies...
INCLUDING DEPENDENCY AptosFramework
INCLUDING DEPENDENCY AptosStdlib
INCLUDING DEPENDENCY MoveStdlib
BUILDING my_coin
package size 2751 bytes
Do you want to submit a transaction for a range of [868300 - 1302400] Octas at a gas unit price of 100 Octas? [yes/no] >
yes
{
"Result": {
"transaction_hash": "xxx",
...
这样,硬币成功地在链上发布,但它没有任何流通。您需要注册您的帐户才能接收铸造的硬币。
$ aptos move run --function-id default::bsc::register
Do you want to submit a transaction for a range of [153100 - 229600] Octas at a gas unit price of 100 Octas? [yes/no] >
yes
{
"Result": {
"transaction_hash": "xxx",
...
请注意,第二个命令中的 Your 地址需要替换为您自己的地址(请参见 )。在浏览器中输入您的地址,然后单击资源选项卡,您可以看到该帐户现在有 100 个 BSC 硬币。account
.aptos/config.yaml
如果您想将硬币转移到另一个帐户,请不要忘记让该帐户注册以使用该硬币。由于该函数是泛型函数,因此需要将泛型参数指定为 和 to_address,如下所示:transfer
BSC
$ aptos move run — function-id 0x1::coin::transfer — type-args BSC-module-address::bsc::BSC — args address:To_address u64:1
此命令将调用硬币模块的功能,将 1 个 BSC 硬币转移到 To_address。下面是函数的函数 ID。请记住,这是硬币的标识符,必须为其指定通用参数。此外,BSC 模块地址应替换为模块发布者帐户地址,该地址在 中分配给 BlockSec。transfer
0x1::coin::transfer
transfer
BlockSec::bsc::BSC
Move.toml
0x5. 下一步是什么
在了解如何创建、管理和与自己的硬币交互之后,我们将演示如何构建第一个 DeFi 基石项目:自动做市商 (AMM)。将涵盖与 Move 开发和安全实践相关的更多有趣主题。敬请期待!
参考
[1] https://aptos.dev/concepts/coin-and-token/aptos-coin/
[2] https://aptos.dev/concepts/coin-and-token/index
[3] https://aptos.dev/concepts/coin-and-token/aptos-token