Factory Pattern

El patrón de factory es un patrón bien conocido en la programación orientada a objetos. Proporciona una abstracción sobre cómo instanciar una clase.

En el caso de los smart contracts, podemos utilizar este patrón definiendo un contrato de factory que tenga la responsabilidad exclusiva de crear y gestionar otros contratos.

Class hash and contract instance

En Starknet, hay una separación entre las clases de contrato y las instancias. Una clase de contrato sirve como plano, definido por el código de bytes subyacente de Cairo, los puntos de entrada del contrato, la ABI y el hash del programa Sierra. La clase del contrato es identificada por un hash de clase. Cuando se desea agregar una nueva clase a la red, primero es necesario declararla.

Cuando se despliega un contrato, es necesario especificar el hash de clase del contrato que se desea desplegar. Cada instancia de un contrato tiene su propio almacenamiento independientemente del hash de clase.

Utilizando el patrón de factory, podemos desplegar múltiples instancias de la misma clase de contrato y manejar las actualizaciones fácilmente.

Minimal example

He aquí un ejemplo mínimo de un contrato de factory que despliega el contrato SimpleCounter:

use starknet::{ContractAddress, ClassHash};

#[starknet::interface]
trait ICounterFactory<TContractState> {
    /// Create a new counter contract from stored arguments
    fn create_counter(ref self: TContractState) -> ContractAddress;

    /// Create a new counter contract from the given arguments
    fn create_counter_at(ref self: TContractState, init_value: u128) -> ContractAddress;

    /// Update the argument
    fn update_init_value(ref self: TContractState, init_value: u128);

    /// Update the class hash of the Counter contract to deploy when creating a new counter
    fn update_counter_class_hash(ref self: TContractState, counter_class_hash: ClassHash);
}

#[starknet::contract]
mod CounterFactory {
    use starknet::{ContractAddress, ClassHash};
    use starknet::syscalls::deploy_syscall;

    #[storage]
    struct Storage {
        /// Store the constructor arguments of the contract to deploy
        init_value: u128,
        /// Store the class hash of the contract to deploy
        counter_class_hash: ClassHash,
    }

    #[constructor]
    fn constructor(ref self: ContractState, init_value: u128, class_hash: ClassHash) {
        self.init_value.write(init_value);
        self.counter_class_hash.write(class_hash);
    }

    #[abi(embed_v0)]
    impl Factory of super::ICounterFactory<ContractState> {
        fn create_counter_at(ref self: ContractState, init_value: u128) -> ContractAddress {
            // Contructor arguments
            let mut constructor_calldata: Array::<felt252> = array![init_value.into()];

            // Contract deployment
            let (deployed_address, _) = deploy_syscall(
                self.counter_class_hash.read(), 0, constructor_calldata.span(), false
            )
                .expect('failed to deploy counter');

            deployed_address
        }

        fn create_counter(ref self: ContractState) -> ContractAddress {
            self.create_counter_at(self.init_value.read())
        }

        fn update_init_value(ref self: ContractState, init_value: u128) {
            self.init_value.write(init_value);
        }

        fn update_counter_class_hash(ref self: ContractState, counter_class_hash: ClassHash) {
            self.counter_class_hash.write(counter_class_hash);
        }
    }
}

Esta fábrica se puede utilizar para desplegar múltiples instancias del contrato SimpleCounter llamando a las funciones create_counter y create_counter_at.

El hash de la clase SimpleCounter se almacena dentro de la fábrica, y se puede actualizar con la función update_counter_class_hash que permite reutilizar el mismo contrato de fábrica cuando se actualiza el contrato SimpleCounter.

Este ejemplo mínimo carece de varias funciones útiles, como el control de acceso, el seguimiento de los contratos desplegados, los eventos, ...

Last change: 2023-10-19, commit: dcadbd1