升级
替换合约类
为了更好地理解升级在 Starknet 中是如何工作的,重要的是要理解合约及其合约类之间的区别。
replace_class_syscall
replace_class
syscall 允许合约通过在部署后替换其类哈希来更新其源代码。
/// 将合约源代码升级到新的合约类。
fn upgrade(new_class_hash: ClassHash) {
assert(!new_class_hash.is_zero(), 'Class hash cannot be zero');
starknet::replace_class_syscall(new_class_hash).unwrap_syscall();
}
如果一个合约在没有这个机制的情况下部署,它的类哈希仍然可以通过 库调用 替换。 |
Upgradeable
组件
OpenZeppelin Contracts for Cairo 提供 可升级 以添加对合约的升级支持。
用法
升级通常是非常敏感的操作,并且通常需要某种形式的访问控制,以避免未经授权的升级。本例中使用 可拥有 模块。
我们将使用以下模块来实现 API 参考部分中描述的 IUpgradeable 接口。 |
#[starknet::contract]
mod UpgradeableContract {
use openzeppelin_access::ownable::OwnableComponent;
use openzeppelin_upgrades::UpgradeableComponent;
use openzeppelin_upgrades::interface::IUpgradeable;
use starknet::ClassHash;
use starknet::ContractAddress;
component!(path: OwnableComponent, storage: ownable, event: OwnableEvent);
component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);
// Ownable Mixin
#[abi(embed_v0)]
impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl<ContractState>;
impl OwnableInternalImpl = OwnableComponent::InternalImpl<ContractState>;
// Upgradeable
impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl<ContractState>;
#[storage]
struct Storage {
#[substorage(v0)]
ownable: OwnableComponent::Storage,
#[substorage(v0)]
upgradeable: UpgradeableComponent::Storage
}
#[event]
#[derive(Drop, starknet::Event)]
enum Event {
#[flat]
OwnableEvent: OwnableComponent::Event,
#[flat]
UpgradeableEvent: UpgradeableComponent::Event
}
#[constructor]
fn constructor(ref self: ContractState, owner: ContractAddress) {
self.ownable.initializer(owner);
}
#[abi(embed_v0)]
impl UpgradeableImpl of IUpgradeable<ContractState> {
fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {
// This function can only be called by the owner
self.ownable.assert_only_owner();
// Replace the class hash upgrading the contract
self.upgradeable.upgrade(new_class_hash);
}
}
}
安全
升级可能是一项非常敏感的操作,在执行升级时,安全性应始终是首要考虑因素。请确保在升级之前彻底审查更改及其后果。需要考虑的一些方面是:
-
可能会影响集成的 API 更改。例如,更改外部函数的参数可能会破坏调用您的合约的现有合约或链下系统。
-
可能会导致数据丢失的存储更改(例如,更改存储槽名称,使现有存储无法访问)。
-
冲突(例如,错误地重用来自另一个组件的相同存储槽)也是可能的,但如果遵循最佳实践,则不太可能发生,例如在存储变量前加上组件的名称(例如
ERC20_balances
)。 -
在 OpenZeppelin Contracts 的版本之间升级之前,请务必检查 向后兼容性。