Saltar al contenido principal

Escribiendo Casos de Prueba

En esta secci贸n, repasaremos las diferentes formas en que podemos escribir casos de prueba para nuestro precompilador con estado.

Agregando Pruebas de Configuraci贸n

La herramienta de generaci贸n de precompiladores tambi茅n genera esqueletos para pruebas unitarias. Las pruebas de configuraci贸n generadas estar谩n en ./precompile/contracts/helloworld/config_test.go para Subnet-EVM y ./helloworld/config_test.go para Precompile-EVM. Principalmente, hay dos funciones que necesitamos probar: Verify y Equal. Verify verifica si el precompilador est谩 configurado correctamente. Equal verifica si el precompilador es igual a otro precompilador. Las pruebas generadas de Verify contienen un caso v谩lido. Puedes agregar m谩s casos inv谩lidos seg煤n tu implementaci贸n. Las pruebas de Equal generan algunos casos inv谩lidos para probar diferentes marcas de tiempo, tipos y casos de AllowList. Puedes verificar cada archivo config_test.go para otros precompiladores en el directorio ./precompile/contracts de Subnet-EVM para m谩s ejemplos.

Agregando Pruebas de Contrato

La herramienta tambi茅n genera pruebas de contrato para asegurarse de que nuestro precompilador funcione correctamente. Las pruebas generadas incluyen casos para probar capacidades de lista de permitidos, costos de gas y llamadas a funciones en modo de solo lectura. Puedes verificar otros archivos contract_test.go en /precompile/contracts. Las pruebas de contrato de Hello World estar谩n en ./precompile/contracts/helloworld/contract_test.go para Subnet-EVM y ./helloworld/contract_test.go para Precompile-EVM. Tambi茅n agregaremos m谩s pruebas para cubrir las funcionalidades de sayHello() y setGreeting(). Las pruebas de contrato se definen en una estructura est谩ndar que cada prueba puede personalizar seg煤n sus necesidades. La estructura de la prueba es la siguiente:

// PrecompileTest es un caso de prueba para un precompilador
type PrecompileTest struct {
// Caller es la direcci贸n del llamador del precompilador
Caller common.Address
// Input son los bytes de entrada sin procesar para el precompilador
Input []byte
// InputFn es una funci贸n que devuelve los bytes de entrada sin procesar para el precompilador
// Si se especifica, se ignorar谩 Input.
InputFn func(t *testing.T) []byte
// SuppliedGas es la cantidad de gas suministrado al precompilador
SuppliedGas uint64
// ReadOnly es si el precompilador debe ser llamado en modo de solo lectura
// Si es verdadero, el precompilador no debe modificar el estado.
ReadOnly bool
// Config es la configuraci贸n a usar para el precompilador
// Debe ser la misma configuraci贸n de precompilador que se usa en el
// configurador del precompilador.
// Si es nulo, no se llamar谩 a Configure.
Config precompileconfig.Config
// BeforeHook se llama antes de que se llame al precompilador.
BeforeHook func(t *testing.T, state contract.StateDB)
// AfterHook se llama despu茅s de que se llame al precompilador.
AfterHook func(t *testing.T, state contract.StateDB)
// ExpectedRes son los bytes de resultado sin procesar esperados devueltos por el precompilador
ExpectedRes []byte
// ExpectedErr es el error esperado devuelto por el precompilador
ExpectedErr string
// BlockNumber es el n煤mero de bloque a usar para el contexto de bloque del precompilador
BlockNumber int64
}

Cada prueba puede poblar los campos de la estructura PrecompileTest para personalizar la prueba. Esta prueba utiliza una funci贸n auxiliar de AllowList allowlist.RunPrecompileWithAllowListTests(t, Module, state.NewTestStateDB, tests) que puede ejecutar todas las pruebas especificadas m谩s suites de pruebas de AllowList. Si no planeas usar AllowList, puedes ejecutarlas directamente de la siguiente manera:

	for name, test := range tests {
t.Run(name, func(t *testing.T) {
test.Run(t, module, newStateDB(t))
})
}

Agregando Pruebas de VM (Opcional)

Esto solo es aplicable para bifurcaciones directas de Subnet-EVM, ya que los archivos de prueba no se exportan directamente en Golang. Si usas Precompile-EVM, puedes omitir este paso.

Las pruebas de VM son pruebas que ejecutan el precompilador llam谩ndolo a trav茅s de la Subnet-EVM. Estas son las pruebas m谩s completas que podemos ejecutar. Si tu precompilador modifica c贸mo funciona la Subnet-EVM, por ejemplo, cambiando las reglas de la cadena de bloques, debes agregar una prueba de VM. Por ejemplo, puedes echar un vistazo a la funci贸n TestRewardManagerPrecompileSetRewardAddress en aqu铆. Para este ejemplo de Hello World, no modificamos ninguna regla de la Subnet-EVM, as铆 que no necesitamos agregar pruebas de VM.

Agregando Contratos de Prueba en Solidity

Agreguemos nuestro contrato de prueba a ./contracts/contracts. 隆Este contrato inteligente nos permite interactuar con nuestro precompilador! Convertimos la direcci贸n del precompilador HelloWorld a la interfaz IHelloWorld. Al hacerlo, helloWorld es ahora un contrato de tipo IHelloWorld y cuando llamamos a cualquier funci贸n en ese contrato, seremos redirigidos a la direcci贸n del precompilador HelloWorld. El siguiente fragmento de c贸digo se puede copiar y pegar en un nuevo archivo llamado ExampleHelloWorld.sol:

//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./IHelloWorld.sol";

// ExampleHelloWorld muestra c贸mo se puede usar el precompilador HelloWorld en un contrato inteligente.
contract ExampleHelloWorld {
address constant HELLO_WORLD_ADDRESS =
0x0300000000000000000000000000000000000000;
IHelloWorld helloWorld = IHelloWorld(HELLO_WORLD_ADDRESS);

function sayHello() public view returns (string memory) {
return helloWorld.sayHello();
}

function setGreeting(string calldata greeting) public {
helloWorld.setGreeting(greeting);
}
}
warning

El precompilador Hello World es un contrato diferente al ExampleHelloWorld y tiene una direcci贸n diferente. Dado que el precompilador usa AllowList para un acceso permitido, cualquier llamada al precompilador, incluida desde ExampleHelloWorld, ser谩 denegada a menos que el llamador se agregue a la AllowList.

Ten en cuenta que este contrato es simplemente un envoltorio y est谩 llamando a las funciones del precompilador. La raz贸n por la que agregamos otro contrato inteligente de ejemplo es tener pruebas m谩s simples y sin estado.

Para el contrato de prueba, escribimos nuestra prueba en ./contracts/test/ExampleHelloWorldTest.sol.

//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../ExampleHelloWorld.sol";
import "../interfaces/IHelloWorld.sol";
import "./AllowListTest.sol";

contract ExampleHelloWorldTest is AllowListTest {
IHelloWorld helloWorld = IHelloWorld(HELLO_WORLD_ADDRESS);

function step_getDefaultHelloWorld() public {
ExampleHelloWorld example = new ExampleHelloWorld();
address exampleAddress = address(example);
assertRole(helloWorld.readAllowList(exampleAddress), AllowList.Role.None);
assertEq(example.sayHello(), "Hello World!");
}

function step_doesNotSetGreetingBeforeEnabled() public {
ExampleHelloWorld example = new ExampleHelloWorld();
address exampleAddress = address(example);
assertRole(helloWorld.readAllowList(exampleAddress), AllowList.Role.None);
try example.setGreeting("testing") {
assertTrue(false, "setGreeting should fail");
} catch {}
}

function step_setAndGetGreeting() public {
ExampleHelloWorld example = new ExampleHelloWorld();
address exampleAddress = address(example);
assertRole(helloWorld.readAllowList(exampleAddress), AllowList.Role.None);
helloWorld.setEnabled(exampleAddress);
assertRole(
helloWorld.readAllowList(exampleAddress),
AllowList.Role.Enabled
);
string memory greeting = "testgreeting";
example.setGreeting(greeting);
assertEq(example.sayHello(), greeting);
}
}

Adding DS-Test Case

Ah, entiendo. Aqu铆 est谩 la traducci贸n manteniendo el enlace original:

Podemos ahora activar este contrato de prueba a trav茅s de pruebas de hardhat. El script de prueba utiliza el marco de pruebas test de Subnet-EVM en ./contracts/test. Puedes encontrar m谩s informaci贸n sobre el marco de pruebas aqu铆.

El script de prueba se ve as铆:

// (c) 2019-2022, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

import { ethers } from "hardhat";
import { test } from "./utils";

// make sure this is always an admin for hello world precompile
const ADMIN_ADDRESS = "0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC";
const HELLO_WORLD_ADDRESS = "0x0300000000000000000000000000000000000000";

describe("ExampleHelloWorldTest", function () {
this.timeout("30s");

beforeEach("Setup DS-Test contract", async function () {
const signer = await ethers.getSigner(ADMIN_ADDRESS);
const helloWorldPromise = ethers.getContractAt(
"IHelloWorld",
HELLO_WORLD_ADDRESS,
signer
);

return ethers
.getContractFactory("ExampleHelloWorldTest", { signer })
.then((factory) => factory.deploy())
.then((contract) => {
this.testContract = contract;
return contract.deployed().then(() => contract);
})
.then(() => Promise.all([helloWorldPromise]))
.then(([helloWorld]) => helloWorld.setAdmin(this.testContract.address))
.then((tx) => tx.wait());
});

test("should gets default hello world", ["step_getDefaultHelloWorld"]);

test(
"should not set greeting before enabled",
"step_doesNotSetGreetingBeforeEnabled"
);

test(
"should set and get greeting with enabled account",
"step_setAndGetGreeting"
);
});

Was this page helpful?