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, ...