Contract Testing

Las pruebas juegan un papel crucial en el desarrollo de software, especialmente para los smart contracts. En esta sección, te guiaremos a través de los conceptos básicos de las pruebas de un smart contracts en Starknet con scarb.

Empecemos con un simple contrato inteligente como ejemplo:

use starknet::ContractAddress;

#[starknet::interface]
trait ISimpleContract<TContractState> {
    fn get_value(self: @TContractState) -> u32;
    fn get_owner(self: @TContractState) -> ContractAddress;
    fn set_value(ref self: TContractState, value: u32);
}

#[starknet::contract]
mod SimpleContract {
    use starknet::{get_caller_address, ContractAddress};

    #[storage]
    struct Storage {
        value: u32,
        owner: ContractAddress
    }

    #[constructor]
    fn constructor(ref self: ContractState, initial_value: u32) {
        self.value.write(initial_value);
        self.owner.write(get_caller_address());
    }

    #[abi(embed_v0)]
    impl SimpleContract of super::ISimpleContract<ContractState> {
        fn get_value(self: @ContractState) -> u32 {
            self.value.read()
        }

        fn get_owner(self: @ContractState) -> ContractAddress {
            self.owner.read()
        }

        fn set_value(ref self: ContractState, value: u32) {
            assert(self.owner.read() == get_caller_address(), 'Not owner');
            self.value.write(value);
        }
    }
}

Ahora, eche un vistazo a las pruebas de este contrato:

#[cfg(test)]
mod tests {
    // Import the interface and dispatcher to be able to interact with the contract.
    use testing_how_to::contract::{
        ISimpleContract, SimpleContract, ISimpleContractDispatcher, ISimpleContractDispatcherTrait
    };

    // Import the deploy syscall to be able to deploy the contract.
    use starknet::class_hash::Felt252TryIntoClassHash;
    use starknet::{
        deploy_syscall, ContractAddress, get_caller_address, get_contract_address,
        contract_address_const
    };

    // Use starknet test utils to fake the transaction context.
    use starknet::testing::{set_caller_address, set_contract_address};

    // Deploy the contract and return its dispatcher.
    fn deploy(initial_value: u32) -> ISimpleContractDispatcher {
        // Set up constructor arguments.
        let mut calldata = ArrayTrait::new();
        initial_value.serialize(ref calldata);

        // Declare and deploy
        let (contract_address, _) = deploy_syscall(
            SimpleContract::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), false
        )
            .unwrap();

        // Return the dispatcher.
        // The dispatcher allows to interact with the contract based on its interface.
        ISimpleContractDispatcher { contract_address }
    }

    #[test]
    #[available_gas(2000000000)]
    fn test_deploy() {
        let initial_value: u32 = 10;
        let contract = deploy(initial_value);

        assert(contract.get_value() == initial_value, 'wrong initial value');
        assert(contract.get_owner() == get_contract_address(), 'wrong owner');
    }

    #[test]
    #[available_gas(2000000000)]
    fn test_set_as_owner() {
        // Fake the caller address to address 1
        let owner = contract_address_const::<1>();
        set_contract_address(owner);

        let contract = deploy(10);
        assert(contract.get_owner() == owner, 'wrong owner');

        // Fake the contract address to address 1
        set_contract_address(owner);
        let new_value: u32 = 20;
        contract.set_value(new_value);

        assert(contract.get_value() == new_value, 'wrong value');
    }

    #[test]
    #[should_panic]
    #[available_gas(2000000000)]
    fn test_set_not_owner() {
        let owner = contract_address_const::<1>();
        set_contract_address(owner);

        let contract = deploy(10);

        let not_owner = contract_address_const::<2>();
        set_contract_address(not_owner);

        let new_value: u32 = 20;
        contract.set_value(new_value);
    }
}

Play with this contract in Remix.

Para definir nuestro test, usamos scarb, que nos permite crear un módulo separado protegido con #[cfg(test)]. Esto asegura que el módulo de prueba sólo se compila cuando se ejecutan pruebas utilizando scarb test.

Cada prueba se define como una función con el atributo #[test]. También puede comprobar si una prueba entra en pánico utilizando el atributo #[should_panic].

Como estamos en el contexto de un smart contract, es esencial establecer el límite de gas. Esto se hace utilizando el atributo #[available_gas(X)] para especificar el límite de gas para una prueba. ¡Esta es también una gran manera de asegurar que las funciones de tu contrato se mantienen por debajo de un cierto límite de gas!

Note: The term "gas" here refers to Sierra gas, not L1 gas

Pasemos ahora al proceso de prueba:

  • Use the deploy function logic to declare and deploy your contract.
  • Use assert to verify that the contract behaves as expected in the given context.

Para que las pruebas resulten más cómodas, el módulo testing de corelib proporciona algunas funciones útiles:

  • set_caller_address(address: ContractAddress)
  • set_contract_address(address: ContractAddress)
  • set_block_number(block_number: u64)
  • set_block_timestamp(block_timestamp: u64)
  • set_account_contract_address(address: ContractAddress)
  • set_max_fee(fee: u128)

También puede necesitar el módulo info de corelib, que le permite acceder a información sobre el contexto de la transacción actual:

  • get_caller_address() -> ContractAddress
  • get_contract_address() -> ContractAddress
  • get_block_info() -> Box<BlockInfo>
  • get_tx_info() -> Box<TxInfo>
  • get_block_timestamp() -> u64
  • get_block_number() -> u64

You can found the full list of functions in the Starknet Corelib repo. You can also find a detailled explaination of testing in cairo in the Cairo book - Chapter 8.

Starknet Foundry

Starknet Foundry es un potente conjunto de herramientas para el desarrollo de contratos inteligentes en Starknet. Ofrece soporte para probar contratos inteligentes Starknet sobre scarb con la herramienta Forge.

Probar con snforge es similar al proceso que acabamos de describir, pero simplificado. Además, hay características adicionales en camino, incluyendo cheatcodes o ejecución de pruebas en paralelo. Recomendamos encarecidamente explorar Starknet Foundry e incorporarlo a tus proyectos.

Si desea información más detallada sobre los contratos de pruebas con Starknet Foundry, consulte el Starknet Foundry Book - Testing Contracts.

Last change: 2023-12-06, commit: 1af1816