Redacción de contratos inteligentes de Aptos DAPP

fondo

En el artículo anterior, presentamos cómo compilar y publicar módulos en aptos, es decir, contratos inteligentes, y después de que se publiquen los contratos inteligentes, pueden interactuar con ellos. Para los usuarios comunes, la interacción con los contratos inteligentes es a través de DAPP. Los próximos artículos presentarán cómo construir un DAPP en aptos desde cero.

Preparación

  • Primero necesitamos crear un directorio my-first-dapp, luego ingresar al directorio para crear un directorio de movimiento para almacenar el código del contrato inteligente
  • Luego usamos el comando aptos move init --name my_todo_list en el directorio de movimiento, que creará un directorio de fuentes y un archivo Move.tom.
¿Qué es el archivo Move.toml?

Un archivo Move.toml es un archivo de configuración, que incluye algunos metadatos, como el nombre, el número de versión y las dependencias del paquete. El contenido del Move.toml que creamos con el comando es el siguiente:

[package]
name = 'my_to_list'
version = '1.0.0'
[dependencies.AptosFramework]
git = 'https://github.com/aptos-labs/aptos-core.git'
rev = 'main'
subdir = 'aptos-move/framework/aptos-framework'

Podemos ver la información del paquete y una dependencia de AptosFramework, donde el atributo de nombre es el atributo que especificamos usando –name, donde la dependencia de AptosFrame apunta a la rama principal del repositorio de github aptos-core/aptos-move/framework/aptos-framework .

directorio de fuentes

El directorio de fuentes es un directorio que contiene una serie de archivos de módulo .move, y luego, cuando queremos usar la línea de comando para compilar, el compilador buscará el directorio de fuentes y su archivo Move.toml relacionado.

Crear un módulo Mover

Como mencionamos en el artículo anterior, necesitamos una cuenta cuando publicamos un módulo Move, por lo que debemos crear una cuenta, una vez que tengamos la clave privada de una cuenta, podemos crear un módulo debajo de la cuenta, los módulos también pueden ser publicado usando esta cuenta.

Use el comando aptos init --network devnet en el directorio de movimiento y presione Entrar para confirmar cuando se le solicite. Este comando crea el directorio .aptos para nosotros, que contiene el archivo config.yaml, que contiene información descriptiva, cuyo contenido es el siguiente:

profiles:
  default:
    private_key: "0x664449b9aefa4694d6871b0025e84dc173a64c58c5dbf413478e79048bc5f6e9"
    public_key: "0xca1b0da9a12a3e51fdab6809e3c4bf2668379bdc62573f80b70da5b5635a0a19"
    account: 6f2dea63c25fcfa946dd54d002e11ec0de56fb37b0cb215396dd079872fc49eb
    rest_url: "https://fullnode.devnet.aptoslabs.com"
    faucet_url: "https://faucet.devnet.aptoslabs.com"

De ahora en adelante, traeremos automáticamente esta información predeterminada cuando usemos la línea de comando en el directorio de movimiento. Cabe señalar que estamos usando la red devnet, y finalmente publicaremos nuestro paquete en la red de prueba.

Como se mencionó anteriormente, nuestro directorio de fuentes contiene archivos de módulo .move, así que agreguemos nuestro primer archivo Move, abramos el archivo Move.toml y agreguemos información. el archivo config.yaml.

[addresses]
todolist_addr='<default-profile-account-address>'

Entonces mi Move.toml cambió de la siguiente manera:

[addresses]
todolist_addr='6f2dea63c25fcfa946dd54d002e11ec0de56fb37b0cb215396dd079872fc49eb'

Luego cree un archivo todolist.move en el directorio de fuentes, y su contenido de código es el siguiente:

module todolist_addr::todolist {
    
}

Un módulo Move debe almacenarse en una dirección, por lo que cuando se libera, se puede acceder al módulo a través de esta dirección. En nuestro módulo, la dirección de la cuenta es todolist_addr, que es lo que configuramos en Move.toml antes, y todolist es el nombre del módulo.

lógica de contrato

Antes de escribir formalmente el código, debemos comprender las funciones del contrato inteligente que debemos escribir. Para que sea más fácil de entender, simplifiqué la lógica del contrato inteligente de la siguiente manera:

  • Una cuenta puede crear una nueva lista
  • Una cuenta puede crear una nueva tarea en la lista, quien cree una nueva tarea enviará una tarea task_created
  • Una cuenta puede marcar sus tareas como completadas

No es necesario crear un evento, pero si un desarrollador quiere monitorear datos, como cuántos usuarios crean nuevas tareas, se puede usar Aotos_Indexer.

Podemos definir una estructura TodoList con los siguientes contenidos:

  • matriz de tareas
  • un nuevo evento de tarea
  • Un contador de tareas, que se usa para registrar la cantidad de tareas creadas, podemos usar esto para distinguir diferentes tareas.

También necesitamos crear una estructura de tareas con los siguientes contenidos:

  • ID de tarea, obtenido del contador de tareas de TodoList1
  • address, crea la dirección de cuenta de la tarea
  • contenido, el contenido de la tarea
  • completado, un indicador booleano si la tarea se completó

Las definiciones de estas dos estructuras son las siguientes:

struct TodoList has key {
        tasks: Table<u64, Task>,
        set_task_event: event::EventHandle<Task>,
        task_counter: u64
    }

    struct Task has store, drop, copy {
        task_id: u64,
        address: address,
        content: String,
        completed: bool
    }

Podemos ver que TodoList tiene la capacidad clave, lo que permite que la estructura se use como un identificador de almacenamiento. En otras palabras, la capacidad clave representa que se puede almacenar en el nivel superior y comportarse como un espacio de almacenamiento. Aquí necesitamos TodoList. llamado Un recurso se almacena en la cuenta del usuario. Cuando una estructura tiene la capacidad clave, la estructura se convertirá en un recurso (recurso). El recurso se almacena bajo una cuenta, por lo que solo puede ser asignado y obtenido por esta cuenta.

La tarea tiene la capacidad de almacenar, soltar y copiar.

  • store, la tarea debe almacenarse en otras estructuras como TodoList
  • copiar, el valor se puede copiar
  • drop, el valor se puede descartar
    Para obtener más detalles sobre las cuatro capacidades de la estructura, puede consultar el artículo anterior sobre Move.

Deberíamos escribir la estructura requerida, ahora intente compilar el código, puede usar aptos move compile en el directorio de movimiento para compilar el código, puede ver que se ha producido un error de tipo Unbound, el error es el siguiente:

error[E03004]: unbound type
  ┌─ /Users/xilou/blockchain/blog/my-first-dapp/move/sources/todolist.move:3:16
  │
3 │         tasks: Table<u64, Task>,
  │                ^^^^^ Unbound type 'Table' in current scope

error[E03002]: unbound module
  ┌─ /Users/xilou/blockchain/blog/my-first-dapp/move/sources/todolist.move:4:25
  │
4 │         set_task_event: Event::EventHandle<Task>,
  │                         ^^^^^ Unbound module alias 'Event'

error[E03004]: unbound type
   ┌─ /Users/xilou/blockchain/blog/my-first-dapp/move/sources/todolist.move:11:18
   │
11 │         content: String,
   │                  ^^^^^^ Unbound type 'String' in current scope

{
  "Error": "Move compilation failed: Compilation error"
}

Esto se debe a que usamos algunos tipos sin importar, por lo que el compilador no puede obtenerlos, agregue el siguiente código en la parte superior del módulo

use aptos_framework::event;
use std::string::String;
use aptos_std::table::Table;

Luego vuelva a compilar para compilar con éxito, y el resultado devuelto es el siguiente

INCLUDING DEPENDENCY AptosFramework
INCLUDING DEPENDENCY AptosStdlib
INCLUDING DEPENDENCY MoveStdlib
BUILDING my_to_list
{
  "Result": [
    "6f2dea63c25fcfa946dd54d002e11ec0de56fb37b0cb215396dd079872fc49eb::todolist"
  ]
}

Crear lista

Lo primero que hace una cuenta es crear una nueva lista, crear una nueva lista requiere enviar una transacción, por lo que necesitamos saber el firmante, es decir, quién envió la transacción, la definición de la función es la siguiente:

public entry fun create_list(account: &signer) {

}

Echemos un vistazo a la clave.

  • entrada, una función de entrada puede ser llamada por una transacción, cuando necesitamos iniciar una transacción en cadena, necesitamos llamar a una función de entrada
  • &signer, el parámetro del cantante es la dirección que será secuestrada por la máquina virtual Move como una transacción firmada

Nuestro código tiene un recurso TodoList, que se almacena en una cuenta, por lo que solo la cuenta puede obtenerlo y asignarlo, lo que significa que creamos un TodoList y debemos asignarlo a una cuenta, y la función create_list debe procesar el TodoList El código completo de la creación es el siguiente:

public entry fun create_list(account: &signer) {
    let task_holer = TodoList {
        tasks: table::new(),
        set_task_event: account::new_event_handle<Task>(account),
        task_count: 0
    };
    move_to(account, tasks_holder);
}

Usamos el módulo de cuenta, por lo que necesitamos usar el siguiente código para agregar

use aptos_framework::account;

crear función de tarea

Como se mencionó anteriormente, necesitamos una función para crear una tarea para que una cuenta pueda crear una nueva tarea. Crear una tarea también requiere enviar una transacción, por lo que necesitamos saber el contenido del firmante y la tarea:

public entry fun create_task(account: &signer, content: String) acquires TodoList {
        //获取地址
        let signer_address = signer::address_of(account);
        //获取TodoList资源
        let todo_list = borrow_global_mut<TodoList>(signer_address);
        //task计数器计数
        let counter = todo_list.task_counter + 1;
        //创建一个新的task
        let new_task = Task {
            task_id: counter,
            address: signer_address,
            content,
            completed: false
        };
        table::upsert(&mut todo_list.tasks, counter, new_task);
        todo_list.task_counter = counter;
        event::emit_event<Task>(
            &mut borrow_global_mut<TodoList>(signer_address).set_task_event,
            new_task,
        )
    }

Como usamos un nuevo módulo, necesitamos importar el firmante y la tabla, podemos usar el siguiente código:

use std::signer;
use aptos_std::table::{Self, Table}; // This one we already have, need to modify it

función de finalización de tareas

También necesitamos una función para marcar la tarea como completada

public entry fun complete_task(account: &signer, task_id: u64) acquires TodoList {
        // 获取signer地址
        let signer_address = signer::address_of(account);
        // 获取TodoList资源
        let todo_list = borrow_global_mut<TodoList>(signer_address);
        // 根据task id获取相应的task
        let task_record = table::borrow_mut(&mut todo_list.tasks, task_id);
        // 更新任务未已完成
        task_record.completed = true;
    }

Entonces también podemos usar aptos move compile para compilar

aumentar la verificación

Nuestra lógica principal ha sido escrita, pero aún esperamos agregar alguna verificación antes de crear nuevas tareas y actualizar tareas, para garantizar que nuestras funciones puedan funcionar normalmente.

public entry fun create_task(account: &signer, content: String) acquires TodoList {
  // gets the signer address
  let signer_address = signer::address_of(account);

  // 验证已经创建了一个列表
  assert!(exists<TodoList>(signer_address), 1);

  ...
}

entrada pública diversión complete_task(cuenta: &firmante,

task_id: u64) acquires TodoList {
  // gets the signer address
  let signer_address = signer::address_of(account);
  // 验证已经创建了列表
  assert!(exists<TodoList>(signer_address), 1);

  let todo_list = borrow_global_mut<TodoList>(signer_address);
  // 验证task存在
  assert!(table::contains(&todo_list.tasks, task_id), 2);

  let task_record = table::borrow_mut(&mut todo_list.tasks, task_id);
  // 验证task未完成
  assert!(task_record.completed == false, 3);

  task_record.completed = true;
}

Puedes ver que assert acepta dos parámetros, el primero es el contenido del cheque, y el segundo es el código de error, para el código de error, es mejor definirlo de antemano.

const E_NOT_INITIALIZED: u64 = 1;
const ETASK_DOESNT_EXIST: u64 = 2;
const ETASK_IS_COMPLETED: u64 = 3;

agregar prueba

La lógica principal se ha completado, ahora necesita agregar pruebas, la función de prueba se puede marcar con #[prueba], agregue el siguiente código al final del código:

#[test]
public entry fun test_flow() {

}

Necesitamos completar las siguientes pruebas.

  • Crear lista
  • crear tarea
  • Tarea de actualización completada

el código se muestra a continuación

#[test(admin = @0x123)]
    public entry fun test_flow(admin: signer) acquires TodoList {
        account::create_account_for_test(signer::address_of(&admin));
        create_list(&admin);

        create_task(&admin, string::utf8(b"new task"));
        let task_count = event::counter(&borrow_global<TodoList>(signer::address_of(&admin)).set_task_event);
        assert!(task == 1, 4);

        let todo_list = borrow_global<TodoList>(signer::address_of(&admin));
        assert!(todo_list.task_counter == 1, 5);
        let task_record = table::borrow(&todo_list.tasks, todo_list.task_count);
        assert!(task_record.task_id == 1, 6);
        assert!(task_record.completed == false, 7);
        assert!(task_record.content == string::utf8(b"new task"), 8);
        assert!(task_record.address == signer::address_of(&admin), 9);

        complete_task(&admin, 1);
        let todo_list = borrow_global<TodoList>(signer::address_of(&admin));
        let task_record = table::borrow(&todo_list.tasks, 1);
        assert!(task_record.task_id == 1, 10);
        assert!(task_record.completed == true, 11);
        assert!(task_record.content == string::utf8(b"new task"), 12);
        assert!(task_record.address == signer::address_of(&admin), 13);
    }

Dado que nuestra prueba se ejecuta fuera del alcance de nuestra cuenta, necesitamos crear una cuenta de prueba. Usé una cuenta de administrador cuya dirección es @ 0x123. Antes de ejecutar la prueba oficialmente, necesitamos importar el módulo usando la siguiente declaración

use std::string::{Self, String}; // already have it, need to modify

Use aptos move test para probar, los resultados son los siguientes

INCLUDING DEPENDENCY AptosFramework
INCLUDING DEPENDENCY AptosStdlib
INCLUDING DEPENDENCY MoveStdlib
BUILDING my_to_list
Running Move unit tests
[ PASS    ] 0x6f2dea63c25fcfa946dd54d002e11ec0de56fb37b0cb215396dd079872fc49eb::todolist::test_flow
Test result: OK. Total tests: 1; passed: 1; failed: 0
{
  "Result": "Success"
}

módulo de liberación

Usamos el comando aptos move compile para compilar el módulo en el directorio move, y el error es el siguiente

  use std::string::{Self, String};
  │              ^^^^^^ Unused 'use' of alias 'string'. Consider removing it

Eso es porque usamos una cadena en el módulo de prueba, pero no se usa en el código de contrato oficial, solo cámbielo a lo siguiente

use std::string::String; // change to this
...
#[test_only]
use std::string; // add this

Use aptos move puhlish para publicar el módulo, presione Enter para continuar cuando se le solicite
, el resultado es el siguiente

{
  "Result": {
    "transaction_hash": "0x0e443ef21c8b19783c06741eb4a5306f11b1529664cf39e4f86fd6679e658686",
    "gas_used": 1675,
    "gas_unit_price": 100,
    "sender": "6f2dea63c25fcfa946dd54d002e11ec0de56fb37b0cb215396dd079872fc49eb",
    "sequence_number": 0,
    "success": true,
    "timestamp_us": 1678615900086281,
    "version": 1605342,
    "vm_status": "Executed successfully"
  }
}

por fin

Este artículo describe principalmente la escritura de contratos inteligentes en DAPP. Para más artículos, puede prestar atención a la cuenta oficial QStack.

Supongo que te gusta

Origin blog.csdn.net/QStack/article/details/129479006
Recomendado
Clasificación