WEB3DEV Español

Gabriella Alexandra Martinez Viloria
Gabriella Alexandra Martinez Viloria

Posted on

Escribe tu primer Contrato Inteligente en Aptos: una guía paso a paso

Este artículo fue escrito por Samundra Karki, y traducido por Gabriella Martinez. Puedes encontrar el artículo original aquí.
Sería genial escucharte en nuestro Discord, puedes contarnos tus ideas, comentarios, sugerencias y dejarnos saber lo que necesitas.
Si prefieres puedes escribirnos a @web3dev_eshttps://twitter.com/web3dev_es en Twitter.

Introducción

Este blog se creó para ayudarte a comenzar a escribir contratos inteligentes en el Blockchain Aptos. Este blog te explicará conceptos claves durante todo el proceso. Estaremos escribiendo un programa “token vesting” en el camino. El programa “token vesting” fue el primer contrato de código abierto de Mokshya Protocol. Puedes ver el código completo aquí.

Mokshya es un protocolo de código abierto que construye contratos inteligentes, SKD y herramientas para desarrolladores en el Blockchain Aptos.

Instalación y Aptos

La documentación en Aptos es muy clara en términos de instalación. Puedes visitarla aquí. Haz click aquí para más guías e instrucciones claras para poder comenzar.

Puedes estudiar más conceptos relacionados con Aptos aquí.

¿Qué es el Token Vesting?

Aptos define los tokens comúnmente conocidos y usados como “monedas” y su token nativo, “APT”, es definido en el módulo de aptos_coin. Puedes crear, acuñar, congelar, transferir y destruir tus propios tokens a través del módulo de monedas.

En Aptos, los Módulos son como contratos inteligentes. Podemos crear y publicar “módulos”. Estos módulos son entidades independientes con la lógica que se puede llamar desde el frontend para ejecutar varias transacciones.

Ahora, digamos que Alice ha dado trabajo por valor de 1000 tokens (monedas) llamadas Mokshya “MOK”, el cual ella tiene que pagarle a Bob en un diferente horario, como se muestra abajo.

  1. Jan 1, 200 MOK
  2. Jan 30, 300 MOK
  3. Feb 13, 400 MOK
  4. Feb 21, 100 MOK

Bob necesita asegurarse que sus pagos se harán durante estos tiempos. Alice teme que si hace todos los pagos al comienzo, entonces Bob puede que no complete el trabajo.

Una solución es encontrar a alguien en quien ambos confíen para hacer el pago programado, según sea necesario. Una mejor solución será utilizar un contrato que hará estos pagos programados. Este es el concepto fundamental del token vesting.

El token vesting es el pago programado sin confianza de una parte a otra.

¿Cómo funciona el Token Vesting?

Alice envía todos los horarios con la cantidad a ser liberada para el blockchain Aptos, el cual es guardado. Junto con eso, Alice deposita 1000 tokens MOK en una cuenta de recursos, la cual actúa como un fideicomiso. Cuando llega el horario Bob puede retirar la cantidad designada. A partir de esto, estamos claros que necesitamos dos funciones principales:

  1. Crear vesting: define los horarios y pagos, la cantidad total a recibir y depositar. function create_vesting
  2. Recibir los pagos: cuando se verifica el destinatario y se pague el importe debido en la fecha. function release_fund

En los próximos pasos, implementaremos estas funciones.

Inicializar e Importar las Dependencias

En primer lugar, crearemos una carpeta llamada token vesting. Ahora, a través de tu terminal, dentro de la carpeta token vesting, usaremos el siguiente comando:

aptos move init --name token-vesting
Enter fullscreen mode Exit fullscreen mode

Si abres la carpeta token vesting, verás que lo siguiente fue creado automáticamente:

Image description

Esta es la estructura del desarrollo del módulo Aptos o desarrollo del contrato inteligente. En la carpeta sources, el módulo está presente. Un módulo individual también puede ser dividido en sub módulos más pequeños, todos están en sources. Move.toml es similar al administrador de paquetes Cargo.toml. Ese paquete define el nombre del módulo y varias dependencias.

Para facilitar, reemplaza Move.toml con lo siguiente:

[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

En vez de local = “../../aptos-core/aptos-move/framework/aptos-framework, usaremos el archivo local donde tu framework aptos está ubicado.

Ahora, estás listo para empezar a escribir contratos inteligentes. En la carpeta sources, crea un archivo llamado token-vestige.move

Definiendo

Al inicio, definimos el programa con el identificador módulo, con el nombre del módulo “token_vesting”. El nombre del módulo debe coincidir con el nombre en la dirección de un segmento del Move.toml. Como explicamos anteriormente, podemos escribir múltiples sub módulos dentro del módulo token_vesting, en este caso, tenemos un sub módulo solitario llamado vesting.

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

Dentro del módulo, definiremos todas las dependencias que requeriremos en el 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

Definiendo la Estructura

Necesitamos guardar los datos relacionados con el contrato vesting para que Aptos provea la opción de estructura, la cual puede ser usada para definir varias estructuras de datos. En nuestro caso:

// Toda la información requerida para el Vesting
   struct VestingSchedule has key,store
   {
       sender: address, 
       receiver: address,
       coin_type:address,
       release_times:vector<u64>,   //Los tiempos para desbloquear      release_amounts:vector<u64>, //La cantidad correspondiente por haber desbloqueado
       total_amount:u64,            // La suma de toda la cantidad liberada  
       resource_cap: account::SignerCapability, // Firmante
       released_amount:u64,         //La suma de la cantidad liberada
   }
Enter fullscreen mode Exit fullscreen mode

En Aptos, la dirección es el identificador único de cada cuenta. Requerimos la dirección del remitente, receptor y coin_type. Si vienes con un entorno en Solana, puedes tomar esto como una “dirección de acuñación de token”. Para nuestro caso, la moneda MOK es requerida en coin_type.

**release_times** es el vector de la marca de tiempo de UNIX en orden ascendiente. La marca de tiempo UNIX correspondiente al caso de Alex y Bob será (tomando el año como 2023 y la hora como 00:00)

  1. Jan 1-1672510500
  2. Jan 30-1675016100
  3. Feb 12-1676139300
  4. Feb 21-1676916900

**release_amounts**es el importe programado con los momentos correspondientes.

**total_amount**es la suma de todos los importes liberados, en nuestro caso, 1000 MOKs.

**resource_cap** representa la capacidad del firmante del fideicomiso o de la cuenta de recursos (daremos más explicaciones luego)

**released_amount** está presente para dar cuenta de la cantidad ya retirada por Bob

_Abilities in Move define el límite de una estructura de datos. En nuestro caso, la estructura de datos VestingSchedule tiene las habilidades de almacenamietos (store) y la clave (key). Por lo tanto, puede ser almaceada en una cuenta. _

//Mapa para almacenar la semilla y la dirección de la cuenta de recursos correspondiente
   struct VestingCap  has key {
       vestingMap: SimpleMap< vector<u8>,address>,
   }
Enter fullscreen mode Exit fullscreen mode

Esta estructura es para guardar la semilla y la dirección de recursos correspondiente. Se utiliza un mapa simple para la eficiencia el cual, fue previamente importado del módulo aptos_std.

A continuación, se muestran los errores escritos manualmente los cuales se explican por sí mismos:

  //errores
   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 error está definido por un número u64 para facilitar la identificación del error mientras se ejecuta la transacción.

Crear la función Vesting

En Aptos, se utilizan diferentes identificadores para definir una función según el acceso otorgado a una función. Como nuestra función necesita ser invocada por el usuario Ales, es definida como la función de entrada. En el move de Aptos, el firmante de la transacción viene como la entrada de la función de entrada y como firmante que, en nuestro caso, es Alice. Como Move requiere que la estructura usada ya esté definida, adquirimos VestingCap. CoinType que, en nuestro caso, es la moneda 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

En primer lugar, necesitamos generar una cuenta de recursos que actuará como el fideicomiso, en otras palabras, vesting. La cuenta de Alice y las semillas son utilizadas para crear una cuenta de recursos. El comando !exists<VestingCap>(account_addr) verifica si la estructura VestingCap ya existe en la cuenta de Alice o no, move_to mueve la estructura en la cuenta de Alice, si aún no existe en la cuenta de Alice. borrow_global_mut trae la referencia mutable de la estructura dentro de la cuenta de Alice y la semilla y la dirección de vesting correspondiente es añadida al mapa simple para accesos futuros.

let account_addr = signer::address_of(account);
       let (vesting, vesting_cap) = account::create_resource_account(account, seeds); //resource account
       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

El vesting_signer_from_cap es la capacidad de firma (signer capbility) de la cuenta de recursos vesting. Es la forma del firma para el vesting del fideicomiso.

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

A continuación se muestra una simple línea de código que utiliza el módulo de vector previamente importado. Verificamos la longitud de release_amount,release_time y si el release_amounts es igual a la suma de la cuenta total o no

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 explicamos antes, derivamos el coin_address a través de una función de ayuda y, toda la información, es almacenada en la cuenta de recursos 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

La función de ayuda para derivar coin_address está definida abajo.

/// Una función de ayuda que retorna la dirección del 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
}
En la definición de una función, el tipo de dato que aparece despues de los dos puntos indica el tipo de valor que devuelve la función. Un valor que se devuelve puede dejarse sin punto y como dentro de la función (expr2 es el tipo de retorno).

Ahora, la única cosa que falta por hacer es transferir la moneda de Alice hacia el fideicomiso vesting. En primer lugar, necesitamos registrar la moneda en la cuenta de recurso. Este paso es necesario para que una cuenta reciba solo las monedas que la cuenta desea. En el siguiente paso, la moneda MOK es transferida a la cuenta de recursos vesting

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

Ahora, como tu función está lista, puedes compilar el módulo.

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

Primero, vamos a crear una cuenta y a designar el dev-net como nuestro cluster.

aptos init

Aptos CLI is now set up for account 20634774e3d40bf68fa86101723f2bc36c7b57bc5220e401475f2f1b27377a10 as profile default! Run `aptos — help` for more information about commands
{
“Result”: “Success”
}
Enter fullscreen mode Exit fullscreen mode

Ahora, puedes usar la dirección obtenida para compilar tu código.

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

Función de liberación de fondos

Esta función es invocada por Bob para obtener sus fondos vested. Como la función necesita información en ambas estructuras, necesitan ser adquiridas como la definición de la función.

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

En las siguientes líneas, tomamos prestada el VestingSchedule de la cuenta de recursos vesting y la capacidad del firmante para liberar los fondos. Del mismo modo, el emisor y receptor son 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

Aquí, la estampa de tiempo actual o de ahora, es derivada del framework de aptos. Ahora, se calcula la cantidad de fondos que el receptor puede recibir hasta este momento. Entonces, si la fecha es Feb 12, la cantidad a ser liberada debería ser la suma de todas las cantidades, en otras palabras, 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

Sin embargo, si Bob ya tiene 500 MOKS liberados, digamos en Jan 30, esa cantida debe ser deducida. Por lo tanto, la cantidad que se liberará será de 400 MOK.

Ahora, como ya hemos explicado las monedas MOKS están registradas en la cuenta de Bob y luego son transferidas desde la cuenta de recursos vesting a la dirección de 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;
Enter fullscreen mode Exit fullscreen mode

Ahora, la amount_to_be_released es añadida a la released_amount. Así que, la próxima vez, sólo tendrá acceso permitido a los fondos restantes.

Módulo de publicación

Ahora, estamos listos para publicar el módulo. Usa el siguiente comando en el terminal:

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

Interactuando con el Módulo

Puedes encontrar el código de prueba dentro de la carpeta de pruebas en el repo.

Creando el vesting

//Alice es la cuenta 1 y Bob es la cuenta 2
await faucetClient.fundAccount(account1.address(), 1000000000);//Airdropping
//Tiempo y Cuentas
const now = Math.floor(Date.now() / 1000)
//Cualquier cantidad discreta y el tiempo correspondiente
//puede ser proveído para tener una variedad de pagos programados
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

Liberar los Fondos

await faucetClient.fundAccount(account2.address(), 1000000000);//Airdropping
 //el receptor recibe los fondos asignados según sea necesario
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

Conclusión

Este es sólo el primer paso en tu camino para escribir módulos move en Aptos. Eres bienvenido a contribuir a las soluciones de código abierto en Mokshya Protocol para embarcarte en la aventura del Desarrollo de Contratos Inteligentes en el Blockchain Aptos.

Discussion (0)