Saltar al contenido principal

ULedger Virtual Machine

La ULedger Virtual Machine (ULVM) permite escribir smart contracts. Los contratos se compilan a WebAssembly (WASM), y la ULVM expone un pequeño conjunto de funciones host que el contrato puede invocar para:

  • leer/escribir el estado del blockchain (almacenamiento clave–valor)
  • registrar mensajes
  • emitir eventos
  • acceder al contexto del contrato (llamante, propietario, altura de bloque)

Modelo de estado: almacenamiento clave–valor​

ULedger almacena el estado del contrato como un mapa clave–valor en el Blockchain. El contrato lee y escribe valores por clave.

Conceptualmente, el estado del contrato se ve así:

{
"state": {
"key1": "value1",
"key2": "value2"
}
}

Entonces, cuando se llama a set_storage("my-key", someValue).


Tipos de datos soportados​

Dado que el estado se serializa, la ULVM soporta un conjunto fijo de tipos (a través del serializador que codifica/decodifica valores):

Null = 0,
Bool = 1,
Int32 = 2,
Int64 = 3,
String = 4,
Bytes = 5,
Array = 6,
Map = 7,

Los métodos auxiliares (como storeInt, retrieveInt, etc.) utilizan este serializador internamente para evitar codificar/decodificar bytes manualmente.


Funciones host de la ULVM (imports)​

En AssemblyScript, las funciones host de la ULVM son (a alto nivel):

  • get_storage: obtiene los bytes del valor para una clave
  • set_storage: almacena bytes de valor bajo una clave
  • delete_storage: elimina una clave del estado
  • has_key: verifica si existe una clave
  • log: escribe un mensaje de registro
  • emit_event: emite un evento con un nombre y un payload de datos
  • get_caller: la cuenta/billetera que está llamando a este contrato
  • get_owner: el propietario del contrato
  • get_block_height: altura de bloque actual

Estas funciones toman punteros (usize) porque WASM pasa datos a través de memoria lineal. En la práctica, normalmente se llama a utilidades envolventes (como storeInt) que gestionan punteros y serialización.


Por qué tipado fuerte (AssemblyScript)​

Los contratos se escriben en AssemblyScript (sintaxis similar a TypeScript compilada a WASM). Debido a que compila a WebAssembly, se usan tipos explícitos como i32, i64, bool, Array<i32>, etc.


Tutorial: Almacenar y recuperar (ejemplo básico)

Este primer ejemplo muestra el flujo más sencillo:

  1. almacenar un i32 bajo una clave
  2. recuperarlo

Paso 1: importar los helpers de la ULVM​

import {
storeInt,
retrieveInt,
storeIntArray,
retrieveIntArray,
} from "../src/ulvm";

Estos helpers son envoltorios delgados alrededor de env.get_storage / env.set_storage, más la serialización.


Paso 2: escribir el contrato​

// The entry file of your WebAssembly module.
import {
storeInt,
retrieveInt,
storeIntArray,
retrieveIntArray,
} from "../src/ulvm";

/**
* Simple direct test function that stores a value and retrieves it.
* Uses a fixed key to keep the example minimal.
*/
export function testStoreAndRetrieve(value: i32): i32 {
const key = "test-key";

// Store the value
storeValue(key, value);

// Retrieve and return the value
return retrieveValue(key);
}

/**
* Store a value in storage with a string key
*/
export function storeValue(key: string, value: i32): void {
storeInt(key, value);
}

/**
* Retrieve a value from storage by string key
*/
export function retrieveValue(key: string): i32 {
return retrieveInt(key);
}

/**
* Test storing and retrieving an array
*/
export function testStoreAndRetrieveArray(): i32 {
const key = "array-key";
const array = [10, 20, 30, 40, 50];

storeArray(key, array);

const retrieved = retrieveArray(key);

// Return the length to verify it worked
return retrieved.length;
}

/**
* Store an array with a string key
*/
export function storeArray(key: string, values: Array<i32>): void {
storeIntArray(key, values);
}

/**
* Retrieve an array by string key
*/
export function retrieveArray(key: string): Array<i32> {
return retrieveIntArray(key);
}

Qué hace este contrato​

testStoreAndRetrieve(value: i32) -> i32​

  • escribe value en el almacenamiento bajo "test-key"
  • lee "test-key" de vuelta
  • devuelve el valor recuperado

Entonces, si se llama a testStoreAndRetrieve(123), el estado termina conceptualmente así:

{
"state": {
"test-key": 123
}
}

testStoreAndRetrieveArray() -> i32​

  • almacena [10,20,30,40,50] bajo "array-key"
  • lo recupera de vuelta
  • devuelve la longitud del array (5)

Compilación: compilar AssemblyScript a WebAssembly

Si se usa la estructura de proyecto estándar de AssemblyScript, el flujo típico es:

1) Instalar AssemblyScript (dependencia de desarrollo)​

npm i -D assemblyscript

2) Colocar el punto de entrada del contrato en assembly/index.ts​

Por ejemplo:

  • assembly/smart-contract.ts (el archivo del contrato)

3) Compilar a .wat y .wasm​

Opción A: comando directo asc​

npx asc assembly/smart-contract.ts -o smart-contract.wasm --textFile smart-contract.wat

Resultado: se obtiene un archivo .wat (ejemplo: smart-contract.wat) que es un archivo de texto que el SDK de ULedger puede desplegar.


Ejecución: Despliegue

Para desplegar el Smart Contract compilado se puede usar el SDK de ULedger.

Para la guía en Go sobre cómo hacer esto, ver la documentación de Despliegue de Smart Contracts.