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.
- Jan 1, 200 MOK
- Jan 30, 300 MOK
- Feb 13, 400 MOK
- 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:
- Crear vesting: define los horarios y pagos, la cantidad total a recibir y depositar. function create_vesting
- 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
Si abres la carpeta token vesting, verás que lo siguiente fue creado automáticamente:
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"}
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 {
}
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};
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
}
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)
- Jan 1-1672510500
- Jan 30-1675016100
- Feb 12-1676139300
- 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>,
}
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;
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 {
}
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);
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);
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);
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,
});
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)
}
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);
Ahora, como tu función está lista, puedes compilar el módulo.
module token_vesting::vesting {
............
............
............
}
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”
}
Ahora, puedes usar la dirección obtenida para compilar tu código.
aptos move compile --named-addresses token_vesting="0xaddress_obtained_in_above_command"
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{
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);
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;
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;
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"
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);
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);
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)