名称: multiversx-factory-manager 描述: 用于从父管理器部署和管理子合约的工厂模式。适用于构建市场、启动板、多租户系统或任何从模板部署子合约的协议。
MultiversX 工厂/管理器模式
从模板部署、跟踪和升级子合约——工厂管理器的三个核心操作。
解决什么问题?
当协议需要部署同一合约的多个实例时(例如,每个用户、市场或池一个),工厂管理器处理部署生命周期:从模板部署、跟踪所有实例以及在模板更改时升级它们。
何时使用
| 场景 | 是否使用工厂? |
|---|---|
| 需要同一合约的多个实例 | 是 |
| 每个用户/实体获得自己的合约 | 是 |
| 单个合约服务所有用户 | 否 |
| 每次部署不同合约类型 | 否—使用单独的部署逻辑 |
核心概念
管理器合约
├── 存储模板地址(用于克隆的代码)
├── 部署:通过 from_source() 从模板创建子合约
├── 跟踪:所有部署子合约的注册表
└── 升级:将新模板应用于现有子合约
操作 1:部署
使用 from_source() 克隆模板部署新子合约:
#[endpoint(deployChild)]
fn deploy_child(
&self,
child_id: ManagedBuffer,
init_arg_a: BigUint,
init_arg_b: ManagedAddress,
) -> ManagedAddress {
let caller = self.blockchain().get_caller();
let template = self.template_address().get();
let metadata = CodeMetadata::UPGRADEABLE
| CodeMetadata::READABLE
| CodeMetadata::PAYABLE;
// 从模板部署
let new_address = self.tx()
.typed(child_proxy::ChildProxy) // ChildProxy 从子合约的 #[multiversx_sc::proxy] 生成
.init(init_arg_a, init_arg_b)
.from_source(template)
.code_metadata(metadata)
.returns(ReturnsNewManagedAddress)
.sync_call();
// 在跟踪中注册
self.child_addresses().insert(new_address.clone());
self.child_by_id(&child_id).set(new_address.clone());
self.deploy_event(&caller, &child_id, &new_address);
new_address
}
CodeMetadata 解释
| 标志 | 效果 | 何时使用 |
|---|---|---|
UPGRADEABLE |
子合约稍后可升级 | 几乎总是—操作 3 需要 |
READABLE |
其他合约可以读取子合约存储 | 当使用跨合约存储读取时 |
PAYABLE |
子合约可以直接接收 EGLD | 当子合约需要接受付款时 |
PAYABLE_BY_SC |
只有其他 SC 可以支付子合约 | 当子合约应仅与合约交互时 |
操作 2:跟踪(注册表)
#[multiversx_sc::module]
pub trait RegistryModule {
// 克隆的模板
#[storage_mapper("templateAddress")]
fn template_address(&self) -> SingleValueMapper<ManagedAddress>;
// 所有部署子合约地址的集合
#[storage_mapper("childAddresses")]
fn child_addresses(&self) -> SetMapper<ManagedAddress>;
// 查找:ID → 子合约地址
#[storage_mapper("childById")]
fn child_by_id(&self, id: &ManagedBuffer) -> SingleValueMapper<ManagedAddress>;
// 管理员集合(谁可以部署/升级)
#[storage_mapper("admins")]
fn admins(&self) -> UnorderedSetMapper<ManagedAddress>;
// 辅助:验证地址是受管理的子合约
fn require_managed_child(&self, address: &ManagedAddress) {
require!(
self.child_addresses().contains(address),
"不是受管理的子合约"
);
}
}
模式:SetMapper 用于完整集合(迭代 + 包含检查)+ SingleValueMapper 用于基于 ID 的查找。这提供了 O(1) 的 ID 查找和所有子合约的迭代。
操作 3:升级
将单个子合约升级到当前模板:
#[endpoint(upgradeChild)]
fn upgrade_child(&self, child_id: ManagedBuffer) {
self.require_admin();
let child_address = self.child_by_id(&child_id).get();
self.require_managed_child(&child_address);
let metadata = CodeMetadata::UPGRADEABLE
| CodeMetadata::READABLE
| CodeMetadata::PAYABLE;
self.tx()
.to(child_address)
.typed(child_proxy::ChildProxy) // ChildProxy 从子合约的 #[multiversx_sc::proxy] 生成
.upgrade()
.code_metadata(metadata)
.from_source(self.template_address().get())
.upgrade_async_call_and_exit();
}
注意:当子合约代理不可用时(例如,来自外部代码的基于模板部署),请使用
raw_deploy()而不是typed()进行部署。
模板管理
#[only_owner]
#[endpoint(setTemplate)]
fn set_template(&self, template_address: ManagedAddress) {
require!(
self.blockchain().is_smart_contract(&template_address),
"地址不是智能合约"
);
self.template_address().set(template_address);
}
管理员层次结构
所有者(管理器部署者)
└── 管理员(可以部署/升级子合约)
└── 用户(与自己的子合约交互)
fn require_admin(&self) {
let caller = self.blockchain().get_caller();
require!(
self.admins().contains(&caller)
|| caller == self.blockchain().get_owner_address(),
"未授权"
);
}
反模式
1. 无注册表跟踪
// 错误—部署而不跟踪使得升级不可能
let addr = self.tx().typed(ChildProxy).init().from_source(template).sync_call();
// 地址在此交易后丢失!
2. 缺少 UPGRADEABLE 标志
// 错误—子合约永远无法升级
let metadata = CodeMetadata::READABLE | CodeMetadata::PAYABLE;
// 应包含 CodeMetadata::UPGRADEABLE
3. 升级而不验证
// 错误—无检查地址是否实际上是受管理的子合约
fn upgrade(&self, address: ManagedAddress) {
self.tx().to(address).typed(ChildProxy).upgrade()
.from_source(self.template_address().get())
.upgrade_async_call_and_exit();
}
模板
#[multiversx_sc::module]
pub trait FactoryModule {
#[storage_mapper("templateAddress")]
fn template_address(&self) -> SingleValueMapper<ManagedAddress>;
#[storage_mapper("childAddresses")]
fn child_addresses(&self) -> SetMapper<ManagedAddress>;
#[storage_mapper("childById")]
fn child_by_id(&self, id: &ManagedBuffer) -> SingleValueMapper<ManagedAddress>;
#[storage_mapper("admins")]
fn admins(&self) -> UnorderedSetMapper<ManagedAddress>;
}