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 de janeiro, 200 MOK
- 30 de janeiro, 300 MOK
- 12 de fevereiro, 400 MOK
- 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:
- Criar o vesting: define o cronograma e os pagamentos, destinatário e valor total de depósito. Função create_vesting.
- 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"}
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 {
}
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};
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
}
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 de janeiro - 1672510500
- 30 de janeiro - 1675016100
- 12 de fevereiro - 1676139300
- 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>,
}
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;
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 {
}
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);
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);
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);
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,
});
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)
}
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);
Agora, já que sua única função está pronta, você pode compilar seu módulo.
module token_vesting::vesting {
............
............
............
}
Em primeiro lugar, vamos criar uma conta e designar dev-net como nosso conjunto.
aptos init
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"
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{
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);
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;
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;
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"
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);
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);
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)