Ethereum पर, contract अपग्रेडेबिलिटी के लिए proxy pattern सबसे आम तरीका है। इस पैटर्न में, एक proxy contract, contract के स्टोरेज को होल्ड करता है और फ़ंक्शन कॉल को एक अलग implementation contract को सौंप (delegate कर) देता है। जब अपग्रेड की आवश्यकता होती है, तो आप एक नया implementation contract डिप्लॉय करते हैं और proxy को उसकी ओर पॉइंट कर देते हैं।
Starknet एक अलग दृष्टिकोण अपनाता है, यह स्टोरेज और एड्रेस को बदले बिना डिप्लॉय किए गए contract के क्लास हैश (class hash) को स्वैप करने के लिए replace_class_syscall का उपयोग करता है। यह लेख कवर करता है कि replace_class_syscall कैसे काम करता है और Starknet पर contract अपग्रेड को कैसे लागू किया जाए।
replace_class_syscall कैसे काम करता है
“Understanding Starknet’s Contract Deployment Model” अध्याय को याद करें कि प्रत्येक contract एक contract class का इंस्टेंस होता है: class में बाइटकोड (bytecode) होता है, और instance अपने एड्रेस के साथ स्टोरेज होल्ड करता है। चूँकि बाइटकोड और स्टोरेज अलग-अलग रहते हैं, इसलिए आप स्टोरेज को सुरक्षित रखते हुए contract instance को एक नई class की ओर पॉइंट कर सकते हैं।
replace_class_syscall का उपयोग करके contract को अपग्रेड करने के लिए, हम नए इम्प्लीमेंटेशन के क्लास हैश को एक आर्ग्युमेंट (new_class_hash: ClassHash) के रूप में पास करते हैं।
replace_class_syscall फ़ंक्शन सिग्नेचर (function signature) है:
fn replace_class_syscall(new_class_hash: ClassHash) -> SyscallResult<()>
यह SyscallResult<()> रिटर्न करता है: एक रिजल्ट टाइप जो सफलता पर Ok(()) या विफलता पर त्रुटि विवरण के साथ Err को रैप (wrap) करता है। Syscall तब विफल हो जाता है जब क्लास हैश Starknet पर डिक्लेयर नहीं किया गया हो या जब कॉलर (caller) के पास अपग्रेड करने की अनुमति न हो।
जब replace_class_syscall सफलतापूर्वक निष्पादित (execute) हो जाता है:
- Contract एड्रेस समान रहता है (भले ही कितने भी अपग्रेड हों)
- सभी स्टोरेज डेटा contract के अपने स्टोरेज में बना रहता है
- जैसे ही निष्पादन (execution) कॉलर पर लौटता है, contract instance नए क्लास हैश से लॉजिक (logic) का उपयोग करना शुरू कर देता है।
अब जब हम समझ गए हैं कि replace_class_syscall कैसे काम करता है, तो आइए एक contract को अपग्रेड करने के लिए इसका उपयोग करें।
replace_class_syscall के साथ Contract Upgrades
हम एक Counter contract को, जो count को 1 से बढ़ाता है और वर्तमान count को प्राप्त करता है, एक नए क्लास इम्प्लीमेंटेशन, Greeter contract में अपग्रेड करेंगे।
Counter Contract
साथ-साथ अनुसरण करने के लिए, एक नया प्रोजेक्ट बनाएँ और उसमें नेविगेट करें:
scarb new counter_upgrade
cd counter_upgrade
फिर एक src/counter.cairo फ़ाइल बनाएँ और नीचे दिया गया कोड जोड़ें। हम upgrade फ़ंक्शन इम्प्लीमेंटेशन पर ध्यान केंद्रित करेंगे।
use starknet::{ClassHash};
#[starknet::interface]
pub trait ICounter<TContractState> {
fn get_count(self: @TContractState) -> u32;
fn increment(ref self: TContractState);
fn upgrade(ref self: TContractState, new_class_hash: ClassHash);
}
#[starknet::contract]
mod Counter {
use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess};
use starknet::syscalls::replace_class_syscall;
use starknet::SyscallResultTrait;
use starknet::{ClassHash, ContractAddress, get_caller_address};
#[storage]
struct Storage {
count: u32,
owner: ContractAddress,
current_class_hash: ClassHash,
}
#[event]
#[derive(Drop, starknet::Event)]
enum Event {
ContractUpgraded: ContractUpgraded,
}
#[derive(Drop, starknet::Event)]
struct ContractUpgraded {
old_class_hash: ClassHash, // Previous implementation class hash
new_class_hash: ClassHash, // New implementation class hash
}
#[constructor]
fn constructor(ref self: ContractState, owner: ContractAddress, initial_class_hash: ClassHash) {
self.count.write(1); // Initialize counter to 1
self.owner.write(owner); // Sets the contract owner
self.current_class_hash.write(initial_class_hash); // Just to track the class hashes
}
#[abi(embed_v0)]
impl CounterImpl of super::ICounter<ContractState> {
// returns the current counter value
fn get_count(self: @ContractState) -> u32 {
self.count.read()
}
// increments the counter by 1
fn increment(ref self: ContractState) {
let current = self.count.read();
self.count.write(current + 1);
}
// FOCUS HERE: upgrades the contract to use a new implementation
fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {
let caller = get_caller_address();
assert(caller == self.owner.read(), 'Only contract owner can upgrade');
let old_class_hash = self.current_class_hash.read();
replace_class_syscall(new_class_hash).unwrap_syscall();
self.current_class_hash.write(new_class_hash);
self.emit(ContractUpgraded { old_class_hash, new_class_hash });
}
}
}
upgrade फ़ंक्शन नए क्लास हैश को एक आर्ग्युमेंट के रूप में स्वीकार करता है और यह सुनिश्चित करता है कि अनधिकृत अपग्रेड को रोकने के लिए कॉलर ही contract owner है।
let caller = get_caller_address();
assert(caller == self.owner.read(), 'Only contract owner can upgrade');
वास्तविक अपग्रेड इस लाइन पर होता है:
replace_class_syscall(new_class_hash).unwrap_syscall();
यदि replace_class_syscall कोई त्रुटि रिटर्न करता है, तो unwrap_syscall() पैनिक (panic) करता है, जिसके कारण ट्रांज़ैक्शन रिवर्ट (revert) हो जाता है। इसका मतलब है कि या तो अपग्रेड सफलतापूर्वक पूरा हो जाता है या ट्रांज़ैक्शन पैनिक करता है, रोल बैक (roll back) हो जाता है और contract को अपरिवर्तित छोड़ देता है।
एक बार अपग्रेड पूरा हो जाने पर, फ़ंक्शन परिवर्तन को लॉग करने के लिए एक ContractUpgraded एमिट (emit) करता है:
self.emit(ContractUpgraded { old_class_hash, new_class_hash });
कंपाइलर को यह बताने के लिए कि बिल्ड (build) में किन मॉड्यूल्स को शामिल करना है, src/lib.cairo की सामग्री को mod counter; से बदलें।
Counter Contract को Declare और Deploy करना
अब आइए अपग्रेड कार्यक्षमता का परीक्षण करने के लिए contract को डिक्लेयर (declare) और डिप्लॉय (deploy) करें।
Counter contract को डिक्लेयर करने के लिए निम्नलिखित कमांड चलाएँ। YOUR_ACCOUNT_NAME को अपने अकाउंट नाम से और YOUR_API_KEY को अपनी Alchemy API कुंजी से बदलें:
sncast \
--account <YOUR_ACCOUNT_NAME> \
declare \
--url https://starknet-sepolia.g.alchemy.com/starknet/version/rpc/v0_10/<YOUR_API_KEY> \
--contract-name Counter

Counter contract को Deploy करें
चूँकि Counter कंस्ट्रक्टर अपग्रेड को ट्रैक करने के लिए प्रारंभिक क्लास हैश की एक आर्ग्युमेंट के रूप में अपेक्षा करता है, इसलिए हम इसे owner एड्रेस के साथ, ऊपर दिए गए डिक्लेरेशन चरण से प्राप्त क्लास हैश पास करते हैं। प्लेसहोल्डर्स को उनके ठोस मूल्यों (concrete values) से बदलते हुए, डिप्लॉय करने के लिए निम्नलिखित कमांड चलाएँ:
sncast \
--account <YOUR_ACCOUNT_NAME> \
deploy \
--class-hash <COUNTER_CLASS_HASH> \
--arguments '<OWNER_ADDRESS>, <COUNTER_CLASS_HASH>' \
--url https://starknet-sepolia.g.alchemy.com/starknet/version/rpc/v0_10/<YOUR_API_KEY>

डिप्लॉयमेंट के बाद, count 1 होना चाहिए क्योंकि हमने इसे कंस्ट्रक्टर में 1 पर इनिशियलाइज़ (initialize) किया था। हम Voyager के रीड contract इंटरफ़ेस के माध्यम से get_count को कॉल करके इसे वेरीफाई कर सकते हैं, जो 1 रिटर्न करता है।

Counter contract को अपग्रेड करने के लिए, हमें एक नए क्लास हैश की आवश्यकता है। हम इसे Greeter नामक एक दूसरा contract डिक्लेयर करके प्राप्त करते हैं।
Greeter Contract बनाना
Greeter contract ग्रीटिंग संदेश सेट और पुनर्प्राप्त करेगा, ग्रीटिंग काउंट को ट्रैक करेगा, और अपग्रेड कार्यक्षमता शामिल करेगा। हम तीन चीज़ों को प्रदर्शित करने के लिए नए Counter संस्करण के बजाय संरचनात्मक रूप से अलग contract का उपयोग कर रहे हैं:
- अपग्रेड्स के दौरान स्टोरेज कैसे संरक्षित रहता है
- फ़ील्ड नाम के टकराव (collisions) कैसे व्यवहार करते हैं
replace_class_syscallको कॉल करने के बाद नया इम्प्लीमेंटेशन ठीक कब प्रभावी होता है
इनमें से प्रत्येक को निम्नलिखित अनुभागों में कवर किया गया है।
उसी counter_upgrade प्रोजेक्ट में एक src/greeter.cairo फ़ाइल बनाएँ और उसमें निम्नलिखित कोड जोड़ें:
use starknet::{ClassHash, ContractAddress};
#[starknet::interface]
pub trait IGreeter<TContractState> {
fn set_greeting(ref self: TContractState, message: ByteArray);
fn get_greeting(self: @TContractState) -> ByteArray;
fn get_greeting_count(self: @TContractState) -> u32;
fn upgrade(ref self: TContractState, new_class_hash: ClassHash);
fn get_owner(self: @TContractState) -> ContractAddress;
}
#[starknet::contract]
mod Greeter {
use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess};
use starknet::syscalls::replace_class_syscall;
use starknet::SyscallResultTrait;
use starknet::{ClassHash, ContractAddress, get_caller_address};
#[storage]
struct Storage {
greeting: ByteArray,
greeting_count: u32,
owner: ContractAddress,
}
#[event]
#[derive(Drop, starknet::Event)]
enum Event {
ContractUpgraded: ContractUpgraded,
}
#[derive(Drop, starknet::Event)]
struct ContractUpgraded {
new_class_hash: ClassHash,
}
#[constructor]
fn constructor(ref self: ContractState, owner: ContractAddress) {
self.owner.write(owner); // Set the contract owner
}
#[abi(embed_v0)]
impl GreeterImpl of super::IGreeter<ContractState> {
// Updates the greeting message and increments the usage counter
fn set_greeting(ref self: ContractState, message: ByteArray) {
self.greeting.write(message);
let current_count = self.greeting_count.read();
let new_count = current_count + 1;
self.greeting_count.write(new_count);
}
// Returns the current greeting message
fn get_greeting(self: @ContractState) -> ByteArray {
self.greeting.read()
}
// Returns how many times the greeting has been updated
fn get_greeting_count(self: @ContractState) -> u32 {
self.greeting_count.read()
}
// Upgrades the contract to use a new implementation
fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {
let caller = get_caller_address();
assert(caller == self.owner.read(), 'Only owner can upgrade');
replace_class_syscall(new_class_hash).unwrap_syscall();
self.emit(ContractUpgraded { new_class_hash });
}
// Returns the contract owner's address
fn get_owner(self: @ContractState) -> ContractAddress {
self.owner.read()
}
}
}
फिर src/lib.cairo में mod greeter; जोड़ें, ताकि हमारे पास lib.cairo में निम्नलिखित हो:
mod counter;
mod greeter;
Counter contract को अपग्रेड करने के लिए क्लास हैश प्राप्त करने हेतु Greeter contract को डिक्लेयर करें:
sncast \
--account <YOUR_ACCOUNT_NAME> \
declare \
--url https://starknet-sepolia.g.alchemy.com/starknet/version/rpc/v0_10/<YOUR_API_KEY> \
--contract-name Greeter

Counter डिप्लॉयमेंट के दौरान owner के रूप में सेट किए गए एड्रेस का उपयोग करके, आर्ग्युमेंट के रूप में Greeter के क्लास हैश के साथ Voyager के “Write Contract” टैब पर upgrade फ़ंक्शन को कॉल करें और ट्रांज़ैक्शन सफल होना चाहिए:

अपग्रेड से पहले और बाद में Contract Storage
अपग्रेड से पहले, Counter contract की यह स्टेट (state) थी:
- count =
1 - owner =
0x014154fb6Dd088b5ceB46df635eCCe6e1a9B0455357931aC7Df4263A7dBf39a9 - current_class_hash =
0xd6574c9c64c779442f0f958db1935708b09d18a4cfb98a86e0ac1ded53ebd9
अपग्रेड करने के बाद, contract Greeter क्लास हैश से कोड निष्पादित करते हुए अपना सारा संग्रहीत डेटा रखता है:
- count =
1 - owner =
0x014154fb6Dd088b5ceB46df635eCCe6e1a9B0455357931aC7Df4263A7dBf39a9 - current_class_hash =
0x6cc6a96920706f49a5579a2c0f235e8480daa047500e4acc3910b5da0c010c0 - greeting =
0 - greeting_count =
0
ध्यान दें कि दोनों contracts में एक owner फ़ील्ड है जो एक ही स्टोरेज लोकेशन पर मैप होता है (sn_keccak("owner") से गणना की गई)।
हम Voyager’s storage query interface का उपयोग करके इसे वेरीफाई कर सकते हैं। Voyager का वर्तमान इंटरफ़ेस आपको एक समय में केवल एक स्टोरेज स्लॉट क्वेरी (query) करने देता है।
एक साथ सभी स्लॉट्स को क्वेरी करने के लिए, contract पेज के शीर्ष पर “View old version of this page” पर क्लिक करें, और क्वेरी टाइप ड्रॉपडाउन से “Struct” चुनें।

फिर इनपुट फ़ील्ड में निम्नलिखित स्ट्रक्ट (struct) पेस्ट करें। ध्यान दें कि यह स्ट्रक्ट Counter और Greeter दोनों क्लास इम्प्लीमेंटेशन्स के स्टोरेज वेरिएबल्स को जोड़ता है:
#[storage]
struct Storage {
count: u32,
owner: ContractAddress,
current_class_hash: ClassHash,
greeting: ByteArray,
owner: ContractAddress,
}
यह देखने के लिए कि contract की संरचना के माध्यम से उसी स्टोरेज को कैसे इंटरप्रेट (interpret) किया जाता है, “Query Struct Data” पर क्लिक करें:

ध्यान दें कि नया क्लास हैश बाद की कॉल्स पर केवल तभी लागू होता है जब वर्तमान फ़ंक्शन upgrade कॉल समाप्त हो जाती है।
इस उदाहरण कोड पर विचार करें जो ठीक से दिखाता है कि अपग्रेड फ़ंक्शन के भीतर अपग्रेड कब प्रभावी होता है:
fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {
let count_before = self.count.read(); // count_before = 1
replace_class_syscall(new_class_hash).unwrap(); // Syscall succeeds
// But this STILL uses the OLD implementation!
self.increment(); // count goes from 1 to 2 (old logic)
let count_after = self.count.read(); // count_after = 2
}
- जब
upgradeकॉल निष्पादित हो रही हो:count_before = 1, फिर पुराने लॉजिक का उपयोग करकेself.increment()के बाद,count_after = 2 - अपग्रेड फ़ंक्शन पूरा होने के बाद: contract की बाद की कॉल्स नए क्लास हैश से कोड निष्पादित करती हैं
इसका मतलब है कि replace_class_syscall नए क्लास हैश को रजिस्टर कर लेता है लेकिन वर्तमान कॉल पुरानी क्लास से कोड निष्पादित करना जारी रखती है। यदि आपको एक ही ट्रांज़ैक्शन के भीतर नई क्लास से कोड निष्पादित करने की आवश्यकता है, तो replace_class_syscall को call_contract_syscall के साथ मिलाएँ।
Upgrades के लिए replace_class_syscall का call_contract_syscall के साथ उपयोग
अपग्रेड करते समय, नई क्लास को रजिस्टर करने के लिए हमेशा पहले replace_class_syscall को कॉल किया जाता है। यदि आपको फिर उसी फ़ंक्शन के भीतर नए इम्प्लीमेंटेशन को तुरंत लागू करने (invoke करने) की आवश्यकता है, तो आपको इसके बाद call_contract_syscall का उपयोग करना होगा।
replace_class_syscall के बाद उसी contract पर कोई भी call_contract_syscall इनवोकेशन (invocation) नए इम्प्लीमेंटेशन का उपयोग करके निष्पादित होगा, भले ही अपग्रेड फ़ंक्शन के भीतर डायरेक्ट फ़ंक्शन कॉल्स अभी भी पुराने इम्प्लीमेंटेशन के तहत चलती हों।
यहाँ self.increment() के बजाय call_contract_syscall का उपयोग करने के लिए उसी अपग्रेड फ़ंक्शन को फिर से लिखा गया है:
fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {
let count_before = self.count.read(); // count_before = 1
replace_class_syscall(new_class_hash).unwrap(); // Registers the new class
let increment_selector = selector!("increment");
call_contract_syscall( // Immediately executes using the NEW implementation
get_contract_address(),
increment_selector, // Dispatches the increment call
array![].span()
).unwrap();
let count_after = self.count.read(); // count_after = 1 (new increment does nothing)
}
self.increment() के विपरीत, जो अपग्रेड फ़ंक्शन की अवधि के लिए पुराने इम्प्लीमेंटेशन का उपयोग करना जारी रखता है, call_contract_syscall increment कॉल को नई क्लास में डिस्पैच (dispatch) करता है। चूँकि replace_class_syscall ने पहले ही उस नई क्लास की ओर पॉइंट करने के लिए contract को अपडेट कर दिया है, call_contract_syscall नए इम्प्लीमेंटेशन को निष्पादित करता है। यही कारण है कि count_after 2 तक बढ़ने के बजाय 1 ही रहता है।
आइए एक अपडेट किए गए Counter contract के साथ ऑन-चेन (on-chain) दोनों अपग्रेड तरीकों को वेरीफाई करें जो उन्हें अलग-अलग फ़ंक्शंस के रूप में लागू करता है।
Version 1: Standard replace_class_syscall
हम उसी फ़ंक्शन के भीतर increment() कॉल करने से पहले और बाद में count की जाँच करके परीक्षण करेंगे कि क्या अपग्रेड तुरंत प्रभावी होता है।
count ट्रैकिंग को शामिल करने के लिए Counter में ContractUpgraded इवेंट को अपडेट करके शुरुआत करें:
#[derive(Drop, starknet::Event)]
struct ContractUpgraded {
old_class_hash: ClassHash,
new_class_hash: ClassHash,
count_before: u32, //ADD THIS
count_after: u32, //ADD THIS
}
upgrade फ़ंक्शन को upgrade_standard से बदलें:
fn upgrade_standard(ref self: ContractState, new_class_hash: ClassHash) {
let caller = get_caller_address();
assert(caller == self.owner.read(), 'Only owner can upgrade');
// Check count before upgrade
let count_before = self.count.read();
let old_class_hash = self.current_class_hash.read();
replace_class_syscall(new_class_hash).unwrap_syscall();
self.current_class_hash.write(new_class_hash);
// Test: Does this use old or new implementation?
self.increment();
// Check count after increment
let count_after = self.count.read();
self.emit(ContractUpgraded {
old_class_hash,
new_class_hash,
count_before,
count_after
});
}
फिर, upgrade_standard फ़ंक्शन को दर्शाने के लिए इंटरफ़ेस को अपडेट करें:
#[starknet::interface]
pub trait ICounter<TContractState> {
fn get_count(self: @TContractState) -> u32;
fn increment(ref self: TContractState);
fn upgrade_standard(ref self: TContractState, new_class_hash: ClassHash); //ADD THIS
}
यहाँ upgrade_standard फ़ंक्शन के साथ पूरा अपडेटेड Counter contract दिया गया है:
use starknet::ClassHash;
#[starknet::interface]
pub trait ICounter<TContractState> {
fn get_count(self: @TContractState) -> u32;
fn increment(ref self: TContractState);
fn upgrade_standard(ref self: TContractState, new_class_hash: ClassHash);
}
#[starknet::contract]
mod Counter {
use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess};
use starknet::syscalls::replace_class_syscall;
use starknet::{ClassHash, ContractAddress, SyscallResultTrait, get_caller_address};
#[storage]
struct Storage {
count: u32,
owner: ContractAddress,
current_class_hash: ClassHash,
}
#[event]
#[derive(Drop, starknet::Event)]
enum Event {
ContractUpgraded: ContractUpgraded
}
#[derive(Drop, starknet::Event)]
struct ContractUpgraded {
old_class_hash: ClassHash,
new_class_hash: ClassHash,
count_before: u32,
count_after: u32,
}
#[constructor]
fn constructor(ref self: ContractState, owner: ContractAddress, initial_class_hash: ClassHash) {
self.count.write(1); // Initialize counter to 1
self.owner.write(owner); // Sets the contract owner
self.current_class_hash.write(initial_class_hash); // to track the class hashes
}
#[abi(embed_v0)]
impl CounterImpl of super::ICounter<ContractState> {
// returns the current counter value
fn get_count(self: @ContractState) -> u32 {
self.count.read()
}
// increments the counter by 1
fn increment(ref self: ContractState) {
let current = self.count.read();
self.count.write(current + 1);
}
fn upgrade_standard(ref self: ContractState, new_class_hash: ClassHash) {
let caller = get_caller_address();
assert(caller == self.owner.read(), 'Only owner can upgrade');
// Check count before upgrade
let count_before = self.count.read();
let old_class_hash = self.current_class_hash.read();
replace_class_syscall(new_class_hash).unwrap_syscall();
self.current_class_hash.write(new_class_hash);
// Test: Does this use old or new implementation?
self.increment();
// Check count after increment
let count_after = self.count.read();
self
.emit(
ContractUpgraded { old_class_hash, new_class_hash, count_before, count_after },
);
}
}
}
अपडेट किए गए Counter contract को फिर से डिक्लेयर और डिप्लॉय करें, फिर Voyager के Write Contract टैब के माध्यम से Greeter के क्लास हैश के साथ upgrade_standard को कॉल करें।

एमिट किया गया (emitted) ContractUpgraded इवेंट दिखाता है कि count 1 से बढ़कर 2 हो गया, जो यह पुष्टि करता है कि अपग्रेड फ़ंक्शन निष्पादन के दौरान पुराने इम्प्लीमेंटेशन के increment() फ़ंक्शन का उपयोग किया गया था:

Version 2: अपग्रेड फ़ंक्शन के भीतर call_contract_syscall का उपयोग करना
अब आइए एक ऐसा अपग्रेड वर्ज़न बनाएँ जो call_contract_syscall का उपयोग करके यह पुष्टि करे कि क्या हम उसी ट्रांज़ैक्शन के भीतर तुरंत नए इम्प्लीमेंटेशन तक पहुँच सकते हैं।
upgrade_standard फ़ंक्शन को निम्नलिखित upgrade_with_call इम्प्लीमेंटेशन से बदलें जो call_contract_syscall का उपयोग करता है:
fn upgrade_with_call(ref self: ContractState, new_class_hash: ClassHash) {
let caller = get_caller_address();
assert(caller == self.owner.read(), 'Only owner can upgrade');
// Check count before upgrade
let count_before = self.count.read();
let old_class_hash = self.current_class_hash.read();
replace_class_syscall(new_class_hash).unwrap_syscall();
self.current_class_hash.write(new_class_hash);
// Test: Call increment using call_contract_syscall to see if new implementation is used
let increment_selector = selector!("increment");
call_contract_syscall(get_contract_address(), increment_selector, array![].span())
.unwrap_syscall();
// Check count after increment
let count_after = self.count.read();
self
.emit(
ContractUpgraded { old_class_hash, new_class_hash, count_before, count_after },
);
}
starknet syscalls मॉड्यूल से call_contract_syscall और starknet से get_contract_address इंपोर्ट करें। अपडेट्स के साथ पूरा contract यह बन जाता है:
use starknet::ClassHash;
#[starknet::interface]
pub trait ICounter<TContractState> {
fn get_count(self: @TContractState) -> u32;
fn increment(ref self: TContractState);
fn upgrade_with_call(ref self: TContractState, new_class_hash: ClassHash);
}
#[starknet::contract]
mod Counter {
use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess};
use starknet::syscalls::{call_contract_syscall, replace_class_syscall};
use starknet::{
ClassHash, ContractAddress, SyscallResultTrait, get_caller_address, get_contract_address,
};
#[storage]
struct Storage {
count: u32,
owner: ContractAddress,
current_class_hash: ClassHash,
}
#[event]
#[derive(Drop, starknet::Event)]
enum Event {
ContractUpgraded: ContractUpgraded,
}
#[derive(Drop, starknet::Event)]
struct ContractUpgraded {
old_class_hash: ClassHash,
new_class_hash: ClassHash,
count_before: u32,
count_after: u32,
}
#[constructor]
fn constructor(ref self: ContractState, owner: ContractAddress, initial_class_hash: ClassHash) {
self.count.write(1); // Initialize counter to 1
self.owner.write(owner); // Sets the contract owner
self.current_class_hash.write(initial_class_hash); // to track the class hashes
}
#[abi(embed_v0)]
impl CounterImpl of super::ICounter<ContractState> {
// returns the current counter value
fn get_count(self: @ContractState) -> u32 {
self.count.read()
}
// increments the counter by 1
fn increment(ref self: ContractState) {
let current = self.count.read();
self.count.write(current + 1);
}
fn upgrade_with_call(ref self: ContractState, new_class_hash: ClassHash) {
let caller = get_caller_address();
assert(caller == self.owner.read(), 'Only owner can upgrade');
// Check count before upgrade
let count_before = self.count.read();
let old_class_hash = self.current_class_hash.read();
replace_class_syscall(new_class_hash).unwrap_syscall();
self.current_class_hash.write(new_class_hash);
// Test: Call increment using call_contract_syscall to see if new implementation is used
let increment_selector = selector!("increment");
call_contract_syscall(get_contract_address(), increment_selector, array![].span())
.unwrap_syscall();
// Check count after increment
let count_after = self.count.read();
self
.emit(
ContractUpgraded { old_class_hash, new_class_hash, count_before, count_after },
);
}
}
}
नए अपडेट किए गए Counter contract को फिर से डिक्लेयर और डिप्लॉय करें, फिर मूल Greeter क्लास हैश का उपयोग करके Greeter में अपग्रेड करने का प्रयास करें। ट्रांज़ैक्शन विफल होना चाहिए:

इस त्रुटि के साथ:
Transaction execution has failed: 0: Error in the called contract (contract addre
ss: 0x014154fb6dd088b5ceb46df635ecce6e1a9b0455357931ac7df4263a7dbf39a9, class has
h: 0x036078334509b514626504edc9fb252328d1a240e4e948bef8d0c08dff45927f, selector:
0x015d40a3d6ca2ac30f4031e42be28da9b056fef9bb7357ac5e85627ee876e5ad): Execution fa
iled. Failure reason:(0x617267656e742f6d756c746963616c6c2d6661696c6564 ('argent/m
ulticall-failed'), 0x0 (''), 0x454e545259504f494e545f4e4f545f464f554e44 ('ENTRYPO
INT_NOT_FOUND'), 0x454e545259504f494e545f4641494c4544 ('ENTRYPOINT_FAILED'), 0x45
4e545259504f494e545f4641494c4544 ('ENTRYPOINT_FAILED')).
ऐसा इसलिए होता है क्योंकि Greeter contract में कोई increment फ़ंक्शन नहीं है, इसलिए अपग्रेड के बाद call_contract_syscall को एंट्री पॉइंट नहीं मिल पाता है। इसका मतलब है कि टारगेट contract को किसी भी ऐसे फ़ंक्शन को लागू करना चाहिए जिसे अपग्रेड के बाद call_contract_syscall इनवोक करता है।
इसे हल करने के लिए, हम Greeter contract में एक खाली increment फ़ंक्शन जोड़ते हैं:
#[starknet::interface]
pub trait IGreeter<TContractState> {
fn set_greeting(ref self: TContractState, message: ByteArray);
fn get_greeting(self: @TContractState) -> ByteArray;
fn get_greeting_count(self: @TContractState) -> u32;
fn upgrade(ref self: TContractState, new_class_hash: ClassHash);
fn get_owner(self: @TContractState) -> ContractAddress;
//NEWLY ADDED
fn increment(ref self: TContractState); // Added for testing
}
और इसे एक खाली फ़ंक्शन के रूप में लागू करते हैं:
fn increment(ref self: ContractState) {
// This function does nothing - just for demonstration
// In a real scenario, this might have different logic than the Counter's increment
}
तो हमारे पास अपडेट किया गया Greeter contract है:
use starknet::{ClassHash, ContractAddress};
#[starknet::interface]
pub trait IGreeter<TContractState> {
fn set_greeting(ref self: TContractState, message: ByteArray);
fn get_greeting(self: @TContractState) -> ByteArray;
fn get_greeting_count(self: @TContractState) -> u32;
fn upgrade(ref self: TContractState, new_class_hash: ClassHash);
fn get_owner(self: @TContractState) -> ContractAddress;
// Empty increment function for testing call_contract_syscall
fn increment(self: @TContractState); // NEWLY ADDED
}
#[starknet::contract]
mod Greeter {
use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess};
use starknet::syscalls::replace_class_syscall;
use starknet::{ClassHash, ContractAddress, SyscallResultTrait, get_caller_address};
#[storage]
struct Storage {
greeting: ByteArray,
greeting_count: u32,
owner: ContractAddress,
}
#[event]
#[derive(Drop, starknet::Event)]
enum Event {
ContractUpgraded: ContractUpgraded,
}
#[derive(Drop, starknet::Event)]
struct ContractUpgraded {
new_class_hash: ClassHash,
}
#[constructor]
fn constructor(ref self: ContractState, owner: ContractAddress) {
self.owner.write(owner);
}
#[abi(embed_v0)]
impl GreeterImpl of super::IGreeter<ContractState> {
fn set_greeting(ref self: ContractState, message: ByteArray) {
self.greeting.write(message);
let current_count = self.greeting_count.read();
let new_count = current_count + 1;
self.greeting_count.write(new_count);
}
fn get_greeting(self: @ContractState) -> ByteArray {
self.greeting.read()
}
fn get_greeting_count(self: @ContractState) -> u32 {
self.greeting_count.read()
}
fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {
let caller = get_caller_address();
assert(caller == self.owner.read(), 'Only owner can upgrade');
replace_class_syscall(new_class_hash).unwrap_syscall();
self.emit(ContractUpgraded { new_class_hash });
}
fn get_owner(self: @ContractState) -> ContractAddress {
self.owner.read()
}
// NEWLY ADDED
fn increment(self: @ContractState) { // This function does nothing - just for demonstration
// In a real scenario, this might have different logic than the Counter's increment
}
}
}
इसका नया क्लास हैश प्राप्त करने के लिए अपडेट किए गए Greeter contract को फिर से डिक्लेयर करें, फिर Voyager के Write Contract टैब के माध्यम से उस क्लास हैश के साथ upgrade_with_call को कॉल करें:

ContractUpgraded event दिखाता है:

count अपरिवर्तित रहा (पहले और बाद में 1) क्योंकि Greeter का खाली increment फ़ंक्शन स्टोरेज को संशोधित नहीं करता है। यह साबित करता है कि:
call_contract_syscallने नए इम्प्लीमेंटेशन को निष्पादित किया: यदि इसने पुरानेCounterलॉजिक का उपयोग किया होता, तो count बढ़कर 2 हो जाता- अपग्रेड syscall के लिए तुरंत प्रभावी था: खाली
Greeterफ़ंक्शन चला, न किCounterका increment लॉजिक
संक्षेप में, अपग्रेड फ़ंक्शन के भीतर कोई भी डायरेक्ट फ़ंक्शन कॉल्स हमेशा पुराने इम्प्लीमेंटेशन के तहत चलती हैं, भले ही वे replace_class_syscall के सापेक्ष (relative) कहीं भी दिखाई दें। एक ही ट्रांज़ैक्शन के भीतर नए इम्प्लीमेंटेशन को निष्पादित करने का एकमात्र तरीका call_contract_syscall है।
Storage Compatibility Consideration (स्टोरेज अनुकूलता पर विचार)
चूँकि दोनों contracts में एक ही नाम (जैसे owner) वाले वेरिएबल्स होते हैं, इसलिए वे उन वेरिएबल नामों से गणना किए गए समान स्टोरेज एड्रेसेस से पढ़ते और लिखते हैं। नए इम्प्लीमेंटेशन से कोई भी राइट्स (writes) उन्हीं स्टोरेज एड्रेसेस को प्रभावित करेंगे, जिससे यदि सावधानीपूर्वक प्रबंधित नहीं किया गया तो संभावित रूप से डेटा का नुकसान या करप्शन हो सकता है।
OpenZeppelin Upgrade Components
Cairo के लिए OpenZeppelin contracts मानकीकृत (standardized) अपग्रेड कंपोनेंट्स प्रदान करते हैं जो सामान्य अपग्रेड पैटर्न्स को संभालते हैं। उनके इम्प्लीमेंटेशन में डायरेक्ट अपग्रेड कार्यक्षमता और एक अपग्रेड-एंड-कॉल पैटर्न दोनों शामिल हैं जो replace_class_syscall को call_contract_syscall के साथ मिलाते हैं। अपग्रेड-एंड-कॉल आपको उसी ट्रांज़ैक्शन के भीतर नए इम्प्लीमेंटेशन से फ़ंक्शंस को अपग्रेड करने और तुरंत निष्पादित करने की अनुमति देता है, ठीक उसी तरह जैसा कि हमने पहले मैन्युअल call_contract_syscall उपयोग के साथ प्रदर्शित किया था।
निष्कर्ष
Contract अपग्रेड्स के लिए Starknet का दृष्टिकोण मौलिक रूप से Ethereum के प्रॉक्सी पैटर्न्स से अलग है। replace_class_syscall के साथ, हमें समान एड्रेस रखते हुए और सभी स्टोरेज डेटा को संरक्षित करते हुए डायरेक्ट कोड रिप्लेसमेंट मिलता है।
याद रखें कि अपग्रेड्स सभी स्टोरेज डेटा को संरक्षित करते हैं; वही contract अब स्टोरेज तक पहुँचने के लिए नए इम्प्लीमेंटेशन के कोड का उपयोग करता है, इसलिए पुराने और नए इम्प्लीमेंटेशन्स के बीच मेल खाने वाले नामों वाले वेरिएबल्स समान स्टोरेज एड्रेसेस को एक्सेस करते हैं। समय (timing) भी मायने रखता है: वर्तमान फ़ंक्शन पूरा होने के बाद अपग्रेड्स प्रभावी होते हैं, हालाँकि call_contract_syscall अपग्रेड किए गए इम्प्लीमेंटेशन को तुरंत एक्सेस कर सकता है।