Factory pattern with upgrades

There are more and more factories that are going to be available on NEAR.

These factories themself are pretty standard:

  • Contract that has precompiled byte code of a contract that it instantiates
  • It has a method to instantiate a new sub-contract with various parameters. There are potentially conditions or who and when can establish them and also there might be some parameters and checks that are required

These pattern with what “Staking Pool Factory” and “Bridge Token Factory” follow.

For Bridge Token Factory, the condition is based on the input arguments of .

Both of the current implementation are missing one crucial part - being able to update the sub-contracts.

For example as staking pool contract evolves, there is no way to allow neither sub-contracts or even factory to instantiate new ones.

Obviously various forms of governance can apply to manage this upgrade, but given NEAR contracts itself are inherently upgradable, this would let to continue evolve the system.

Because factory itself doesn’t usually needs to be updated, even though general smart contract upgradability pattern can be followed, in this case a bit more custom version will allow to easily upgrade the factories and sub-contracts.


Factory pattern

Let’s standardize factory patterns. Also include this trait and basic implementing in the standard pattern library.

Additionally, we want to allow to upgrade the existing sub contract that were already instantiated from the factory.

The may have their own governance, but governing factory and allowing them to be upgraded in a lazy way would allow for more flexibility. For example in the case of staking pools - they have their own owner who can be updating them when it make sense.

Hence, factory exposes interface for current bytecode hash of the code. This would allow to upgrade the sub-contract by checking the factory.

trait ContractFactory {
    /// Bytecode of the sub-contracts
    fn new(owner: AccountId, <bytecode>);

    /// Creates new sub-contract. It's always XYZ.<current account id>
    /// XYZ is inferred from the arguments
    fn create(&self, <args>);

    /// Callback on success or failure of creation
    fn on_create(&self, <callback args>);

    /// Allows to stage new deployable bytecode. Can only be called by "owner".
    /// Owner can be any other account, like multisig, DAO, etc. Voting or any other sort of thing can be implemented there.
    fn update(<new bytecode>);

    /// Returns current bytecode hash
    fn get_bytecode_hash() -> CryptoHash;

The sub-contracts then can be have their own method update(<new bytecode>), which calls factory, validates that bytecode hash matches and upgrades itself + potentially runs data migration.

1 Like