让合约支持升级
Cairo合约分为两部分:合约类和合约实例。这种划分遵循面向对象编程语言中使用的类似概念,其中我们区分对象的定义和实现。
合约类是合约的定义:它指定合约的行为方式。它包含基本信息,如开罗字节码、提示信息、入口点名称以及明确定义其语义的所有内容。
为了识别不同的合约类别,Starknet 为每个类别分配了一个唯一的标识符:类别哈希。合约实例是一个已部署的合约,对应于特定的合约类。将其视为 Java 等语言中的对象实例。
每个类都通过其类哈希来标识,这类似于面向对象编程语言中的类名。合约实例是一个类对应的部署合约。
可以通过调用 replace_class_syscall 函数将已部署的合约升级到新版本。通过使用此函数,您可以更新与已部署合约关联的类哈希,从而有效地升级其实现。但是,这不会修改合约的存储,因此合约中存储的所有数据将保持不变。
solidity合约需要特别注意合约数据的存储槽slot位置,cairo合约根据变量的名称区分
solidity合约需要用到delegatecall实现,cairo合约直接变更class_hash
Scarb.toml添加oz引用
[dependencies]
openzeppelin = { git = "<https://github.com/OpenZeppelin/cairo-contracts.git>", tag = "v0.8.0-beta.0" }
components提供了一种乐高组合合约代码的方式,教程https://book.cairo-lang.org/zh-cn/ch99-01-05-00-components.html
使用oz upgradeable_component
use openzeppelin::upgrades::Upgradeable as upgradeable_component;
use openzeppelin::upgrades::interface::IUpgradeable;
use starknet::ClassHash;
use starknet::ContractAddress;
component!(path: upgradeable_component, storage: upgradeable, event: UpgradeableEvent);
impl UpgradeableInternalImpl = upgradeable_component::InternalImpl<ContractState>;
#[storage]
struct Storage {
balance: felt252,
#[substorage(v0)]
upgradeable: upgradeable_component::Storage
}
#[event]
#[derive(Drop, starknet::Event)]
enum Event {
#[flat]
UpgradeableEvent: upgradeable_component::Event
}
#[external(v0)]
impl UpgradeableImpl of IUpgradeable<ContractState> {
fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {
// Replace the class hash upgrading the contract
self.upgradeable._upgrade(new_class_hash);
}
}
#[generate_trait]会自动生成trait声明,减少模板代码
#[external(v0)]
#[generate_trait]
impl HelloStarknetExtImpl of IHelloStarknetExtTrait {
fn version(self: @ContractState) -> u8 {
0
}
}
#[starknet::interface]
trait IHelloStarknet<TContractState> {
fn increase_balance(ref self: TContractState, amount: felt252);
fn get_balance(self: @TContractState) -> felt252;
}
#[starknet::contract]
mod HelloStarknet {
use openzeppelin::upgrades::Upgradeable as upgradeable_component;
use openzeppelin::upgrades::interface::IUpgradeable;
use starknet::ClassHash;
use starknet::ContractAddress;
component!(path: upgradeable_component, storage: upgradeable, event: UpgradeableEvent);
/// Upgradeable
impl UpgradeableInternalImpl = upgradeable_component::InternalImpl<ContractState>;
#[storage]
struct Storage {
balance: felt252,
#[substorage(v0)]
upgradeable: upgradeable_component::Storage
}
#[event]
#[derive(Drop, starknet::Event)]
enum Event {
#[flat]
UpgradeableEvent: upgradeable_component::Event
}
#[external(v0)]
impl HelloStarknetImpl of super::IHelloStarknet<ContractState> {
fn increase_balance(ref self: ContractState, amount: felt252) {
assert(amount != 0, 'Amount cannot be 0');
self.balance.write(self.balance.read() + amount);
}
fn get_balance(self: @ContractState) -> felt252 {
self.balance.read()
}
}
#[external(v0)]
impl UpgradeableImpl of IUpgradeable<ContractState> {
fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {
// Replace the class hash upgrading the contract
self.upgradeable._upgrade(new_class_hash);
}
}
#[external(v0)]
#[generate_trait]
impl HelloStarknetExtImpl of IHelloStarknetExtTrait {
fn version(self: @ContractState) -> u8 {
0
}
}
}
更改version为1
#[starknet::interface]
trait IHelloStarknet<TContractState> {
fn increase_balance(ref self: TContractState, amount: felt252);
fn get_balance(self: @TContractState) -> felt252;
}
#[starknet::contract]
mod HelloStarknet {
use openzeppelin::upgrades::Upgradeable as upgradeable_component;
use openzeppelin::upgrades::interface::IUpgradeable;
use starknet::ClassHash;
use starknet::ContractAddress;
component!(path: upgradeable_component, storage: upgradeable, event: UpgradeableEvent);
/// Upgradeable
impl UpgradeableInternalImpl = upgradeable_component::InternalImpl<ContractState>;
#[storage]
struct Storage {
balance: felt252,
#[substorage(v0)]
upgradeable: upgradeable_component::Storage
}
#[event]
#[derive(Drop, starknet::Event)]
enum Event {
#[flat]
UpgradeableEvent: upgradeable_component::Event
}
#[external(v0)]
impl HelloStarknetImpl of super::IHelloStarknet<ContractState> {
fn increase_balance(ref self: ContractState, amount: felt252) {
assert(amount != 0, 'Amount cannot be 0');
self.balance.write(self.balance.read() + amount);
}
fn get_balance(self: @ContractState) -> felt252 {
self.balance.read()
}
}
#[external(v0)]
impl UpgradeableImpl of IUpgradeable<ContractState> {
fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {
// Replace the class hash upgrading the contract
self.upgradeable._upgrade(new_class_hash);
}
}
#[external(v0)]
#[generate_trait]
impl HelloStarknetExtImpl of IHelloStarknetExtTrait {
fn version(self: @ContractState) -> u8 {
1
}
}
}
将v0版本代码拷贝到lib.cairo中
//declare v0
sncast declare -c HelloStarknet
command: declare
class_hash: 0x371eb3aa14ef0ae390a127824f92acbdb3093cd5b632f49906cb67ae6466886
transaction_hash: 0x29187fcda2c7633a3a64eb86e604fbce445fa9f2da9e9e44769a85e272166c1
//deploy v0
sncast deploy -g 0x371eb3aa14ef0ae390a127824f92acbdb3093cd5b632f49906cb67ae6466886
command: deploy
contract_address: 0x341a70ff2a47079b125a83a72179b5328db92b7217f52bdfba1a41376d30234
transaction_hash: 0x25dacab9a45b22f3bfc57dd2138749d02db4f870cc8dee707b2a80d2406da98
//call version=0
sncast call -a 0x341a70ff2a47079b125a83a72179b5328db92b7217f52bdfba1a41376d30234 -f version
command: call
response: [0x0]
将v1版本代码拷贝到lib.cairo中
//declare v1
sncast declare -c HelloStarknet
command: declare
class_hash: 0x768bd8e78e6fbdaad33c9c8d484024822d3d8dd562d6fcd656f8bbebc0ff9be
transaction_hash: 0x53f5c8b7eb2f19ae5019018aad8ce8afe7eed4d621453efff6d5589bf40c048
//invoke update
sncast invoke -a 0x341a70ff2a47079b125a83a72179b5328db92b7217f52bdfba1a41376d30234 -f upgrade -c 0x768bd8e78e6fbdaad33c9c8d484024822d3d8dd562d6fcd656f8bbebc0ff9be
command: invoke
transaction_hash: 0x3d1f0c7602005ade04d183cd033342d2d73af01e71694680f9da321f0078137
//call version=1
sncast call -a 0x341a70ff2a47079b125a83a72179b5328db92b7217f52bdfba1a41376d30234 -f version
command: call
response: [0x1]
也可通过浏览器查询合约class_hash历史版本

d