WEB3DEV

Cover image for Escreva seu primeiro contrato inteligente na Aptos - Um guia passo a passo
Isabela Curado Nehme
Isabela Curado Nehme

Posted on

Escreva seu primeiro contrato inteligente na Aptos - Um guia passo a passo

27 de fevereiro de 2023

Introdução

Este blog foi criado para lhe ajudar a começar a escrever contratos inteligentes na Blockchain Aptos. O blog explicará os principais conceitos ao longo do processo. Estaremos escrevendo um programa “Token-Vesting” (impedimento de token) ao longo do caminho. O programa “Token-Vesting” foi o primeiro contrato de código aberto (open-source) do Protocolo Mokshya. Você pode ver o código completo aqui.

Mokshya é um protocolo de código aberto para criação de contratos inteligentes, SDKs e ferramentas de desenvolvedores na Blockchain Aptos.

Instalação e Aptos

A documentação na Aptos é bastante clara em termos de instalação. Você pode visitar aqui. Para mais instruções guiadas e claras para dar início, comece por aqui.

Você pode estudar mais conceitos envolvidos na Aptos aqui.

O que é Token-Vesting?

A Aptos define tokens comumente usados e conhecidos como “moedas” e seu token nativo - “APT” - é definido no módulo aptos_coin. Você pode criar, cunhar, congelar, transferir e destruir seus próprios tokens através deste módulo de moeda.

Na Aptos, os módulos são como contratos inteligentes. Podemos criar e publicar “módulos”. Esses módulos são entidades independentes com lógica que podem ser chamados do front-end para executar várias transações.

Agora, digamos que Alex tenha um trabalho no valor de 1000 tokens (moeda) chamado Mokshya “MOK” que ele tem que pagar a Bob em um cronograma diferente como abaixo.

  1. 1 de janeiro, 200 MOK
  2. 30 de janeiro, 300 MOK
  3. 12 de fevereiro, 400 MOK
  4. 21 de fevereiro, 100 MOK

Bob precisa de segurança de que seus pagamentos serão feitos durante estes tempos. Alex teme que se ele fizer todos os pagamentos no início, Bob pode não completar o trabalho.

Uma solução é encontrar alguém que ambos confiem para realizar o pagamento agendado conforme necessário. Uma solução muito melhor será usar um contrato que realize esses pagamentos agendados. Este é o conceito fundamental de Token-Vesting.

Token-Vesting é o pagamento agendado sem confiança de uma parte para outra parte.

Como o Token-Vesting funciona?

Alex envia todos os cronogramas com o valor de liberação para a Blockchain Aptos, que fica salvo. Junto com isso, Alex deposita os 1000 tokens MOK em uma conta de recurso, que atua como uma garantia (ou caução) sem confiança. Quando chegar o dia programado, Bob pode retirar a quantia designada. A partir disso, estamos claros que precisamos de duas funções principais:

  1. Criar o vesting: define o cronograma e os pagamentos, destinatário e valor total de depósito. Função create_vesting.
  2. Receber os pagamentos: onde o destinatário é verificado e o valor devido na data é pago. Função release_fund.

Nos próximos passos, vamos implementar essas funções.

Inicializando e importando dependências

Primeiro de tudo, crie uma pasta chamada token-vesting. Agora, através do seu terminal dentro da pasta token-vesting, use o seguinte comando:

aptos move init --name token-vesting

Se você abrir a pasta token-vesting, verá o seguinte, gerado automaticamente:

Esta é a estrutura de desenvolvimento do módulo da Aptos ou desenvolvimento de contrato inteligente. Na pasta sources (fontes), o módulo está presente. Um módulo individual também pode ser quebrado em sub-módulos menores, todos eles estão na pasta sources. O Move.toml é semelhante ao gerenciador de pacotes Carto.toml. Ele define o nome do módulo e várias dependências.

Para facilitar, substitua o Move.toml pelo seguinte:

[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"}
Enter fullscreen mode Exit fullscreen mode

No lugar de - local = “../../aptos-core/aptos-move/framework/aptos-framework”, use a pasta local onde seus frameworks da Aptos estão localizados.

Agora, você está pronto para mudar em direção à escrita de contratos inteligentes. Na pasta sources, crie um arquivo chamado “token-vesting.move”.

Definindo

Na iniciação, definimos o programa com o identificador module com o nome de módulo “token_vesting”. O nome do módulo deve corresponder ao nome no endereço de um segmento de Move.toml. Como explicado anteriormente, podemos escrever vários sub-módulos dentro do módulo token_vesting, nesse caso, temos um único sub-módulo chamado vesting.

module token_vesting::vesting {
}
Enter fullscreen mode Exit fullscreen mode

Dentro do módulo, definimos todas as dependências que vamos necessitar no módulo.

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};
Enter fullscreen mode Exit fullscreen mode

Definindo a estrutura

Precisamos salvar os dados referentes ao contrato de vesting para que a Aptos forneça a opção struct (de estrutura) que pode ser usada para definir várias estruturas de dados. Em nosso caso,

// Todas as informações necessárias para o Vesting
   struct VestingSchedule has key,store
   {
       sender: address, 
       receiver: address,
       coin_type:address,
       release_times:vector<u64>,   //Os tempos para desbloqueio
       release_amounts:vector<u64>, //O valor correspondente para ser desbloqueado
       total_amount:u64,            //Soma de todos os valores de liberação   
       resource_cap: account::SignerCapability, //Signatário
       released_amount:u64,         //Soma de valores liberados
   }
Enter fullscreen mode Exit fullscreen mode

Na Aptos, o address (endereço) é o identificador exclusivo de cada conta. Nós necessitamos do endereço do sender (remetente), receiver (destinatário) e coin_type (tipo da moeda). Se você vier de uma experiência com a Solana, pode considerar isso como um “endereço de cunhagem de token”. Para o nosso caso, a moeda MOK é o coin_type requisitado.

release_times é o vetor da marca temporal UNIX em ordem crescente. A marca temporal UNIX correspondente ao caso de Alex e Bob será (considerando o ano de 2023 e o tempo de 00:00):

  1. 1 de janeiro - 1672510500
  2. 30 de janeiro - 1675016100
  3. 12 de fevereiro - 1676139300
  4. 21 de fevereiro - 1676916900

release_amounts é o valor agendado nos tempos correspondentes.

total_amount é a soma de todos os release amounts (valores agendados no tempo), no nosso caso, 1000 MOKs.

resource_cap representa a capacidade do signatário de garantia ou conta de recurso (mais explicações posteriormente).

released_amount está presente para contar o valor já retirado por Bob.

Na Move, as habilidades definem o limite de uma estrutura de dados. No nosso caso, a estrutura VestingSchedule tem as habilidades de armazenamento e chave. Portanto, pode ser armazenada em uma conta.

//Mapa para armazenar a semente e o endereço da conta de recurso correspondente
   struct VestingCap  has key {
       vestingMap: SimpleMap< vector<u8>,address>,
   }
Enter fullscreen mode Exit fullscreen mode

Esta estrutura é para salvar a semente e o endereço de recurso correspondente. Um mapa simples é usado aqui para eficiência, que foi previamente importado do módulo aptos_std.

A seguir, estão os erros manualmente escritos que são auto-explicativos.

//erros
   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;
Enter fullscreen mode Exit fullscreen mode

Cada erro é definido com um número u64 para facilitar a identificação do erro enquanto uma transação está em execução.

Criar a função Vesting

Na Aptos, identificadores diferentes são usados para definir uma função a partir do acesso concedido a uma função. Como nossa função precisa ser chamada pelo usuário Alex, esta é definida como a função de entrada. Na Move da Aptos, o signatário da transação vem como a entrada da função de entrada como &signer, que, em nosso caso, é o Alex. Como a Move necessita que a estrutura usada já esteja definida, nós adquirimos o VestingCap. O CoinType, no nosso caso, é a moeda 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 {

}
Enter fullscreen mode Exit fullscreen mode

Em primeiro lugar, precisamos gerar uma conta de recurso que vai atuar como a garantia (ou caução), ou seja, vesting. A conta e as sementes de Alex são usadas para criar uma conta de recurso. O comando !exists(account_addr) verifica se VestingCap struct já existe na conta de Alex ou não, move_to move a estrutura para a dentro da conta de Alex se ainda não existir na conta de Alex. borrow_global_mut traz a referência mutável da estrutura para dentro da conta de Alex e a semente e o endereço de vesting correspondente são adicionados ao mapa simples para acesso futuro.

let account_addr = signer::address_of(account);
       let (vesting, vesting_cap) = account::create_resource_account(account, seeds); //conta de recurso
       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);
Enter fullscreen mode Exit fullscreen mode

O vesting_signer_from_cap é a capacidade do signatário da conta de recurso - vesting. É o formulário do signer para a garantia de vesting.

let vesting_signer_from_cap = account::create_signer_with_capability(&vesting_cap);
Enter fullscreen mode Exit fullscreen mode

Abaixo está uma linha simples de código que utiliza o módulo vetorial previamente importado. Verificamos o comprimento de release_amount, release_time e se o release_amounts é igual à soma do valor total ou não.

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);
Enter fullscreen mode Exit fullscreen mode

Como explicado anteriormente, derivamos o coin_address através de uma função auxiliar e toda a informação está salva na conta de recurso - 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,
});
Enter fullscreen mode Exit fullscreen mode

A função auxiliar para derivar o coin_address é definida abaixo.

/// Uma função auxiliar que retorna o endereço de CoinType.
fun coin_address<CoinType>(): address {
      let type_info = type_info::type_of<CoinType>();
      type_info::account_address(&type_info)
   }
Enter fullscreen mode Exit fullscreen mode

fun name(inputs): datatype {

expr1;

expr2

}

Na função de definição datatype, depois de dois pontos é retornado type (tipo). Um dado de retorno pode ser deixado sem um ponto e vírgula dentro da função (expr2 é o tipo de retorno).

Agora, a única coisa que resta é transferir a moeda de Alex para a garantia de vesting. Primeiramente, precisamos registrar a moeda na conta de recurso. Este passo torna necessário que uma conta receba apenas as moedas que a conta deseja. No próximo passo, a moeda MOK é transferida para a conta de recurso vesting.

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

Agora, já que sua única função está pronta, você pode compilar seu módulo.

module token_vesting::vesting {
............
............
............
}
Enter fullscreen mode Exit fullscreen mode

Em primeiro lugar, vamos criar uma conta e designar dev-net como nosso conjunto.

aptos init 
Enter fullscreen mode Exit fullscreen mode

A Aptos CLI está agora configurada para a conta 20634774e3d40bf68fa86101723f2bc36c7b57bc5220e401475f2f1b27377a10 como perfil padrão! Execute aptos — help para mais informações sobre comandos
{
“Result”: “Success”
}

Agora, você pode usar o endereço obtido para compilar seu código.

aptos move compile --named-addresses token_vesting="0xaddress_obtained_in_above_command"
Enter fullscreen mode Exit fullscreen mode

Função Release Fund (Liberar Fundo)

Esta função é chamada por Bob para receber seu fundo investido. Como a função necessita de informações sobre ambas as estruturas, elas precisam ser adquiridas na definição da função.

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

Nas linhas a seguir, pegamos emprestado o VestingSchedule da conta de recurso vesting e seu signer-capability (capacidade de signatário) para liberar os fundos. De forma semelhante, o remetente e o destinatário são verificados.

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);
Enter fullscreen mode Exit fullscreen mode

Aqui, a marca temporal atual é derivada do framework da Aptos. Agora, o valor dos fundos que o destinatário pode receber até o momento é calculado. Portanto, se a data é 12 de fevereiro, o valor a ser liberado deve ser a soma de todos os valores, ou seja, 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;
Enter fullscreen mode Exit fullscreen mode

Mas se o Bob já tiver retirado, digamos, em 30 de janeiro, o valor liberado na época de 500 MOKs deve ser deduzido. Portanto, o valor a ser liberado será 400.

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;
Enter fullscreen mode Exit fullscreen mode

Agora, o amount_to_be_released é adicionado ao released_amount. Portanto, no próximo momento, ele apenas terá permissão para acessar os fundos remanescentes.

Publicando o Módulo

Agora, estamos prontos para publicar o módulo. Use o comando a seguir no terminal:

aptos move publish --named-addresses token_vesting="0xaddress_obtained_in_above_command"
Enter fullscreen mode Exit fullscreen mode

Interagindo com o Módulo

Você pode encontrar o código de teste dentro da pasta de testes no repositório.

Criação do vesting:

//Alex é a conta 1 e Bob é a conta 2
await faucetClient.fundAccount(account1.address(), 1000000000);//Airdropping
//Tempo e valores
const now = Math.floor(Date.now() / 1000)
//Qualquer valor discreto e tempo correspondente
//pode ser fornecido para obter uma variedade de cronograma de pagamentos
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);
Enter fullscreen mode Exit fullscreen mode

Liberação dos fundos:


await faucetClient.fundAccount(account2.address(), 1000000000);//Airdropping
 //o destinatário recebe o fundo alocado como requisitado
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);
Enter fullscreen mode Exit fullscreen mode

Conclusão

É apenas o primeiro passo na sua jornada de escrita de módulos Move na Aptos. Você está convidado a contribuir com as soluções de código aberto no Protocolo Mokshya para navegar na jornada do desenvolvimento de contratos inteligentes na Blockchain Aptos.

Esse artigo foi escrito por Samundra Karki e traduzido por Isabela Curado Nehme. Seu original pode ser lido aqui.

Top comments (0)