Aptos合约开发新手指南

本文旨在帮助你开始在Aptos区块链上编写智能合约。我们将解释过程中的关键概念,并逐步编写一个“代币锁定期”程序。

Aptos合约开发新手指南
一键发币: Aptos | X Layer | SUI | SOL | BNB | ETH | BASE | ARB | OP | Polygon | Avalanche | 用AI学区块链开发

本博客旨在帮助你开始在Aptos区块链上编写智能合约。博客将解释过程中的关键概念。我们将逐步编写一个“代币锁定期”程序。“代币锁定期”程序是Mokshya Protocol的第一个开源合同。您可以在这里查看完整代码。

Mokshya是一个开源协议,在Aptos区块链上构建智能合约、SDK和开发工具。

1、安装和Aptos

Aptos的文档在安装方面非常清晰有关更详细和清晰的入门指导,请从这里开始。 你可以在这里学习更多与Aptos相关的概念。

2、什么是 代币 锁定期?

Aptos将常用的和使用的代币定义为“硬币”,其原生代币“Aptos”在aptos_coin模块中定义。你可以通过coin模块创建、铸造、冻结、转移和销毁自己的代币。

在Aptos中,模块就像智能合约。我们可以创建和发布“模块”。这些模块是具有逻辑的独立实体,可以从前端调用以执行各种交易。

现在,假设Alice给了价值1000个代币(硬币)的工钱,名为Mokshya “MOK”,他要在不同的时间表上支付给Bob如下:

  1. 1月1日,200 MOK
  2. 1月30日,300 MOK
  3. 2月12日,400 MOK
  4. 2月21日,100 MOK

Bob需要确保他的付款会在这些时候进行。Alice担心如果他在开始时就支付所有款项,Bob可能无法完成工作。

一个解决方案是找到双方都信任的人来按要求进行计划的付款。更好的解决方案是使用一个合同来执行这些计划的付款。这就是代币锁定期的基本概念。

代币锁定期是从一方到另一方的无信任计划付款。

3、代币锁定期是如何工作的?

Alice将所有的计划和释放金额发送到Aptos区块链并保存。同时,Alice将1000 MOK代币存入一个资源账户,该账户作为无信任的托管账户。当计划到来时,Bob可以提取指定的金额。从这我们清楚我们需要两个主要功能:

  1. 创建锁定期:定义计划和付款,接收者和存款总额。function create_vesting
  2. 接收付款:验证接收者并在当天支付应付金额。function **release_fund.**在下一步中,我们将实现这些功能

4、初始化和导入依赖项

首先,创建一个名为token-vesting的文件夹。现在,在终端中进入token-vesting文件夹,使用以下命令:

 aptos move init --name token-vesting

如果您打开token-vesting文件夹,将看到以下自动生成的内容:

这是Aptos模块或智能合约开发的开发结构。在sources文件夹中,有模块。一个单独的模块可以分解成更小的子模块,它们都在sources中。Move.toml类似于包管理器Cargo.toml。它定义了模块名称和各种依赖项。

为了方便,将Move.toml替换为以下内容:

[package]  
name = 'token-vesting'  
version = '1.0.0'  

[addresses]  
token_vesting = "_"  
Std = "0x1"  
aptos_std = "0x1"  

[dependencies]  
AptosFramework = { local = "../../aptos-core/aptos-move/framework/aptos-framework"}

local = "../../aptos-core/aptos-move/framework/aptos-framework"替换为你的aptos-framework所在的本地文件夹。

现在,我们已准备好转向编写智能合约。在sources文件夹中,创建一个名为“token-vesting.move”的文件。

5、定义

在开始时,我们使用module标识符定义程序,模块名为“token_vesting”。模块名必须与Move.toml中地址段的名称匹配。如前所述,我们可以在token_vesting模块内编写多个子模块,在这种情况下,我们有一个名为vesting的单个子模块。

module token_vesting::vesting {  
}

在模块内部,我们定义模块中所需的全部依赖项,


    use std::signer;      
    use aptos_framework::account;  
    use std::vector;  
    use aptos_framework::managed_coin;  
    use aptos_framework::coin;  
    use aptos_std::type_info;  
    use aptos_std::simple_map::{Self, SimpleMap};

6、定义结构

我们需要保存关于代币锁定期合同的数据,为此Aptos提供了struct选项,可用于定义各种数据结构。在我们的情况下,

// 所有用于Vesting的信息  
    struct VestingSchedule has key,store  
    {  
        sender: address,    
        receiver: address,   
        coin_type:address,  
        release_times:vector<u64>,   //解锁的时间  
        release_amounts:vector<u64>, //对应解锁的金额  
        total_amount:u64,            //所有释放金额的总和     
        resource_cap: account::SignerCapability, // Signer  
        released_amount:u64,         //已释放金额的总和  
    }

在Aptos中,address是每个帐户的唯一标识符。我们需要senderreceivercoin_type的地址。如果你来自Solana的背景,可以将其视为“代币mint地址”。对于我们的案例,需要MOK代币的coin_type。

release_times 是升序排列的UNIX时间戳向量。对于Alex和Bob的情况,对应的UNIX时间戳为(将年份设为2023,时间为00:00)

  1. 1月1日–1672510500
  2. 1月30日–1675016100
  3. 2月12日–1676139300
  4. 2月21日–1676916900

release_amounts 是对应时间的预定金额。

total_amount 是所有释放金额的总和,在我们的例子中是1000 MOK。

resource_cap 表示托管或资源账户的签名者能力(稍后会进一步解释)

released_amount 用于记录Bob已经提取的金额

Move中的能力定义了数据结构的边界。在我们的例子中,结构VestingSchedule具有存储和键的能力。因此,它可以存储在账户中。
    // 存储种子和相应资源账户地址的映射  
    struct VestingCap  has key {  
        vestingMap: SimpleMap< vector<u8>,address>,  
    }

这个结构用于保存种子和相应的资源地址。这里使用了简单映射,之前从aptos_std模块导入。

以下是手动编写的错误,自我解释:

    //错误  
    const ENO_INSUFFICIENT_FUND:u64=0;  
    const ENO_NO_VESTING:u64=1;  
    const ENO_SENDER_MISMATCH:u64=2;  
    const ENO_RECEIVER_MISMATCH:u64=3;  
    const ENO_WRONG_SENDER:u64=4;  
    const ENO_WRONG_RECEIVER:u64=5;

每个错误都定义了一个u64数字,以便在执行事务时更容易识别错误。

7、创建锁定期函数

在Aptos中,不同的标识符用于根据函数授予的访问权限定义函数。由于我们的函数需要由用户Ale调用,它被定义为入口函数。在Aptos move中,交易签署人作为入口函数的输入&signer,在我们的情况下是Alice。由于Move要求使用的结构已经定义,我们获取VestingCapCoinType在我们的情况下是Mokshya代币。

    public entry fun create_vesting<CoinType>(  
        account: &signer,  
        receiver: address,  
        release_amounts:vector<u64>,  
        release_times:vector<u64>,  
        total_amount:u64,  
        seeds: vector<u8>  
    )acquires VestingCap {  

}

首先,我们需要生成一个资源账户,该账户将作为托管账户即vesting。使用Alice的账户和种子创建资源账户。!exists(account_addr) 命令验证VestingCap struct是否已经在Alice的账户中存在,move_to 如果Alice的账户中不存在该结构,则将其移动到Alice的账户中。borrow_global_mut 将结构的可变引用带入Alice的账户,并将种子和相应的vesting地址添加到简单映射中以便将来访问。

        let account_addr = signer::address_of(account);  
        let (vesting, vesting_cap) = account::create_resource_account(account, seeds); //资源账户  
        let vesting_address = signer::address_of(&vesting);  
        if (!exists<VestingCap>(account_addr)) {  
            move_to(account, VestingCap { vestingMap: simple_map::create() })  
        };  
        let maps = borrow_global_mut<VestingCap>(account_addr);  
        simple_map::add(&mut maps.vestingMap, seeds,vesting_address);

vesting_signer_from_cap 是资源账户- vesting 的签名者能力。它是托管 vestingsigner 形式。

let vesting_signer_from_cap = account::create_signer_with_capability(&vesting_cap);

下面是利用之前导入的向量模块的一行代码。我们验证release_amount,release_times的长度以及release_amounts是否等于总金额。

let length_of_schedule =  vector::length(&release_amounts);  
let length_of_times = vector::length(&release_times);  
assert!(length_of_schedule==length_of_times,ENO_INSUFFICIENT_FUND);  
let i=0;  
let total_amount_required=0;  
while ( i < length_of_schedule )  
{  
    let tmp = *vector::borrow(&release_amounts,i);  
    total_amount_required=total_amount_required+tmp;  
    i=i+1;  
};  
assert!(total_amount_required==total_amount,ENO_INSUFFICIENT_FUND);

如前所述,我们通过辅助函数推导出coin_address,并将所有信息保存在资源账户- vesting 中。

let released_amount=0;  
let coin_address = coin_address<CoinType>();  
move_to(&vesting_signer_from_cap, VestingSchedule{  
sender:account_addr,  
receiver,  
coin_type:coin_address,   
release_times,  
release_amounts,  
total_amount,  
resource_cap:vesting_cap,  
released_amount,  
});

辅助函数用于推导coin_address的定义如下。

 /// 一个返回CoinType地址的辅助函数。  
fun coin_address<CoinType>(): address {  
       let type_info = type_info::type_of<CoinType>();  
       type_info::account_address(&type_info)  
    }
fun 名称(输入): 数据类型 { 表达式1; 表达式2 }
在函数定义中,冒号后的数据类型是返回类型。返回数据可以在函数内部不带分号(表达式2 是返回类型)。

现在剩下的唯一事情就是将代币从Alice转移到vesting托管账户。首先,我们需要在资源账户中注册代币。这一步使得账户只能接收账户想要的代币。下一步,将MOK代币转移到vesting资源账户

    managed_coin::register<CoinType>(&vesting_signer_from_cap);   
    coin::transfer<CoinType>(account, vesting_address, total_amount);

现在,函数已经准备好了,你可以编译模块了。

module token_vesting::vesting {  
............  
............  
............  
}

首先,让我们创建一个账户并指定dev-net作为我们的集群

aptos init 
现在,Aptos CLI已为账户20634774e3d40bf68fa86101723f2bc36c7b57bc5220e401475f2f1b27377a10 设置为默认配置文件!运行 aptos — help 了解有关命令的更多信息
{
“Result”: “Success”
}

现在,您可以使用获得的地址来编译您的代码

aptos move compile --named-addresses token_vesting="0xaddress_obtained_in_above_command"

8、释放资金功能

该函数由Bob调用以获取其锁定的资金。由于该函数需要有关两个结构的信息,因此在函数定义时需要获取它们。

public entry fun release_fund<CoinType>(  
        receiver: &signer,  
        sender: address,  
        seeds: vector<u8>  
    )acquires VestingSchedule,VestingCap{

在以下几行中,我们从vesting资源账户借用VestingSchedule及其signer-capability以释放资金。同样,验证发送者和接收者。

let receiver_addr = signer::address_of(receiver);  
assert!(exists<VestingCap>(sender), ENO_NO_VESTING);  
let maps = borrow_global<VestingCap>(sender);  
let vesting_address = *simple_map::borrow(&maps.vestingMap, &seeds);  
assert!(exists<VestingSchedule>(vesting_address), ENO_NO_VESTING);    
let vesting_data = borrow_global_mut<VestingSchedule>(vesting_address);   
let vesting_signer_from_cap = account::create_signer_with_capability(&vesting_data.resource_cap);  
assert!(vesting_data.sender==sender,ENO_SENDER_MISMATCH);  
assert!(vesting_data.receiver==receiver_addr,ENO_RECEIVER_MISMATCH);

在这里,现在的时间戳是从aptos-framework中得出的。现在,计算接收者到目前为止可以收到的资金数额。所以,如果日期是2月12日,应释放的金额应为900 MOKS

let length_of_schedule =  vector::length(&vesting_data.release_amounts);  
let i=0;  
let amount_to_be_released=0;  
let now = aptos_framework::timestamp::now_seconds();  
while (i < length_of_schedule)  
{  
    let tmp_amount = *vector::borrow(&vesting_data.release_amounts,i);  
    let tmp_time = *vector::borrow(&vesting_data.release_times,i);  
    if (tmp_time<=now)  
    {  
        amount_to_be_released=amount_to_be_released+tmp_amount;  
    };  
    i=i+1;  
};  
amount_to_be_released=amount_to_be_released-vesting_data.released_amount;

但是,如果Bob在1月30日已经释放了500 MOKS,那么此时应扣除已释放的金额。因此,应释放的金额将是400。

现在,如前所述,MOKS代币已在Bob的账户中注册,然后从vesting - 资源账户转移到Bob的地址。

 if (!coin::is_account_registered<CoinType>(receiver_addr))  
     {  
        managed_coin::register<CoinType>(receiver);   
     };  
coin::transfer<CoinType>(&vesting_signer_from_cap,receiver_addr,amount_to_be_released);  
vesting_data.released_amount=vesting_data.released_amount+amount_to_be_released;

现在,amount_to_be_released 添加到 released_amount 中。因此,下次他只能访问剩余的资金。

9、发布模块

现在,我们准备好发布模块。在终端中使用以下命令:

aptos move publish --named-addresses token_vesting="0xaddress_obtained_in_above_command"

10、与模块交互

你可以在repo中的tests文件夹中找到测试代码。

创建锁定期

//alice 是账户1,bob 是账户2  
await faucetClient.fundAccount(account1.address(), 1000000000);//空投  
//时间和金额  
const now = Math.floor(Date.now() / 1000)  
//任何离散金额和对应时间  
//可以提供以获得多种付款计划  
const release_amount =[10000, 50000, 10000, 30000];  
const release_time_increment =[ 3, 20, 30];  
var release_time:BigInt[]=[BigInt(now)]  
release_time_increment.forEach((item) => {  
  let val=BigInt(now+item);  
  release_time.push(val);  
});  
const create_vesting_payloads = {  
  type: "entry_function_payload",  
  function: pid+"::vesting::create_vesting",  
  type_arguments: ["0x1::aptos_coin::AptosCoin"],  
  arguments: [account2.address(),release_amount,release_time,100000,"xyz"],  
};  
let txnRequest = await client.generateTransaction(account1.address(), create_vesting_payloads);  
let bcsTxn = AptosClient.generateBCSTransaction(account1, txnRequest);  
await client.submitSignedBCSTransaction(bcsTxn);

释放资金


await faucetClient.fundAccount(account2.address(), 1000000000);//空投  
  //接收者按需分配资金  
const create_getfunds_payloads = {  
    type: "entry_function_payload",  
    function: pid+"::vesting::release_fund",  
    type_arguments: ["0x1::aptos_coin::AptosCoin"],  
    arguments: [account1.address(),"xyz"],  
  };  
let txnRequest = await client.generateTransaction(account2.address(), create_getfunds_payloads);  
let bcsTxn = AptosClient.generateBCSTransaction(account2, txnRequest);  
await client.submitSignedBCSTransaction(bcsTxn);

11、结束语

这只是你在Aptos上编写move模块旅程的第一步。欢迎你在Mokshya Protocol的开源解决方案中做出贡献,以在Aptos区块链上进行智能合约开发之旅。


原文链接:Write Your First Smart Contract On Aptos — A Step-by-Step Guide

DefiPlot翻译整理,转载请标明出处

免责声明:本站资源仅用于学习目的,也不应被视为投资建议,读者在采取任何行动之前应自行研究并对自己的决定承担全部责任。