将 ERC165 迁移到 SRC5
在智能合约生态系统中,能够查询合约是否支持给定接口是一项极其重要的功能。
在 v0.7.0 之前的 Cairo 合约的初始内省设计遵循 Ethereum 的 EIP-165。
由于 Cairo 语言的演进引入了原生类型,我们需要一种为 Cairo 生态系统量身定制的内省解决方案:https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-5.md[SNIP-5] 标准。
SNIP-5 允许接口 ID 计算使用 Cairo 类型和 Starknet keccak (sn_keccak
) 函数。
有关该决策的更多信息,请参阅 Starknet Shamans 提案 或 双重内省检测 讨论。
如何迁移
从 ERC165 迁移到 SRC5 涉及四个主要步骤:
-
将 SRC5 集成到合约中。
-
注册 SRC5 ID。
-
添加一个
migrate
函数来应用内省更改。 -
升级合约并调用
migrate
。
以下指南将通过示例介绍这些步骤。
组件集成
第一步是将必要的组件集成到新合约中。
合约应包括新的内省机制 SRC5Component。
它还应包括 InitializableComponent,该组件将在 migrate
函数中使用。
这是设置:
#[starknet::contract]
mod MigratingContract {
use openzeppelin_introspection::src5::SRC5Component;
use openzeppelin_security::initializable::InitializableComponent;
component!(path: SRC5Component, storage: src5, event: SRC5Event);
component!(path: InitializableComponent, storage: initializable, event: InitializableEvent);
// SRC5
#[abi(embed_v0)]
impl SRC5Impl = SRC5Component::SRC5Impl<ContractState>;
impl SRC5InternalImpl = SRC5Component::InternalImpl<ContractState>;
// Initializable
impl InitializableInternalImpl = InitializableComponent::InternalImpl<ContractState>;
#[storage]
struct Storage {
#[substorage(v0)]
src5: SRC5Component::Storage,
#[substorage(v0)]
initializable: InitializableComponent::Storage
}
#[event]
#[derive(Drop, starknet::Event)]
enum Event {
#[flat]
SRC5Event: SRC5Component::Event,
#[flat]
InitializableEvent: InitializableComponent::Event
}
}
接口注册
为了成功地将 ERC165 迁移到 SRC5,合约需要使用 SRC5 注册合约支持的接口 ID。
对于此示例,假设此合约支持 IERC721 和 IERC721Metadata 接口。
合约应实现一个 InternalImpl
并添加一个函数来注册这些接口,如下所示:
#[starknet::contract]
mod MigratingContract {
use openzeppelin_token::erc721::interface::{IERC721_ID, IERC721_METADATA_ID};
(...)
#[generate_trait]
impl InternalImpl of InternalTrait {
// Register SRC5 interfaces
fn register_src5_interfaces(ref self: ContractState) {
self.src5.register_interface(IERC721_ID);
self.src5.register_interface(IERC721_METADATA_ID);
}
}
}
由于新合约集成了 SRC5Component
,因此它可以利用 SRC5 的 register_interface 函数来注册支持的接口。
迁移初始化器
接下来,合约应该定义并公开一个迁移函数,该函数将调用 register_src5_interfaces
函数。
由于 migrate
函数将是可公开调用的,因此它应包含某种 访问控制,以便只有允许的地址才能执行迁移。
最后,migrate
应包含重新初始化检查,以确保它不能被多次调用。
如果原始合约在任何时候实现了 Initializable 并调用了 initialize 方法,则此时 InitializableComponent 将不可用。
相反,合约可以从 InitializableComponent 中获得启发并创建自己的初始化机制。
|
#[starknet::contract]
mod MigratingContract {
(...)
#[external(v0)]
fn migrate(ref self: ContractState) {
// WARNING: Missing Access Control mechanism. Make sure to add one
// WARNING: If the contract ever implemented `Initializable` in the past,
// this will not work. Make sure to create a new initialization mechanism
self.initializable.initialize();
// Register SRC5 interfaces
self.register_src5_interfaces();
}
}