工厂模式

工厂模式是面向对象编程中众所周知的模式。它提供了有关如何实例化类的抽象。

在智能合约里,我们可以通过定义一个工厂合约来使用这种模式,该合约全权负责创建和管理其他合约。

类哈希(Class hash)和合约实例

在Starknet中,合约的类和实例是分开的。合约类充当蓝图,由底层 Cairo 字节码、合约的入口点、ABI 和 Sierra 程序哈希定义。合约类由类哈希标识。当您想向网络添加一个新类时,首先需要声明它。

部署合约时,需要指定要部署的合约的类哈希值。合约的每个实例都有自己的存储,这与类哈希无关。

使用工厂模式,我们可以部署同一合约类的多个实例,并轻松处理升级。

最小范例

下面是部署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);
        }
    }
}

此工厂可用于通过调用SimpleCountercreate_counter函数来部署create_counter_at合约的多个实例。

SimpleCounter类哈希存储在工厂内部,可以使用update_counter_class_hash 函数进行升级,该函数允许在升级SimpleCounter 合约时重用相同的工厂合约。

这个最小的范例缺少几个有用的功能,例如访问控制、跟踪已部署的合约、事件......

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