यह लेख दिखाता है कि Starknet के लिए एक डिप्लॉय करने योग्य Cairo कॉन्ट्रैक्ट कैसे बनाया जाए। एक साधारण स्केच से शुरू करते हुए, हम एक वर्किंग कॉन्ट्रैक्ट बनाने के लिए धीरे-धीरे फीचर्स जोड़ेंगे जो एक Cairo कॉन्ट्रैक्ट के मुख्य बिल्डिंग ब्लॉक्स को प्रदर्शित करेगा।
इस कॉन्ट्रैक्ट में एक काउंटर वेरिएबल होगा जिसे किसी भी अमाउंट (मात्रा) से बढ़ाया जा सकता है और इसकी वैल्यू प्राप्त करने के लिए एक फंक्शन होगा।
हमारे कॉन्ट्रैक्ट का पहला वर्ज़न
mod Counter {
fn increase_counter(amount: felt252) {
// TODO
}
fn get_counter() -> felt252 {
// TODO
}
}
ऊपर दिए गए कोड में निम्नलिखित फीचर्स शामिल हैं:
- एक मॉड्यूल ब्लॉक, जिसे
modकीवर्ड द्वारा दर्शाया गया है। प्रत्येक Cairo कॉन्ट्रैक्ट एक मॉड्यूल के अंदर लिखा जाता है। यह Solidity मेंcontractकीवर्ड के समान है, और मॉड्यूल का नाम कुछ भी हो सकता है। - दो फंक्शन्स: एक काउंटर बढ़ाने के लिए और दूसरा इसकी वर्तमान वैल्यू प्राप्त करने के लिए।
Counter कॉन्ट्रैक्ट के लिए एक trait परिभाषित करके एक “interface” जोड़ना
Solidity में, एक इंटरफेस फंक्शन्स का एक सेट परिभाषित करता है जिसे एक कॉन्ट्रैक्ट को लागू (implement) करना ही चाहिए। कॉन्ट्रैक्ट्स के लिए इंटरफेस अनिवार्य नहीं हैं, लेकिन उनके उपयोग को प्रोत्साहित किया जाता है।
Cairo में, इसी विचार को एक trait का उपयोग करके दर्शाया जाता है, जो फंक्शन्स के इम्प्लीमेंटेशन को प्रदान किए बिना उनकी सूची को परिभाषित करता है। इस अर्थ में, एक Cairo trait वही भूमिका निभाता है जो Solidity में एक इंटरफेस निभाता है।
हालांकि, यह स्पष्ट करना महत्वपूर्ण है कि एक trait को अपने आप कॉन्ट्रैक्ट इंटरफेस नहीं माना जाता है। हमें trait को एक इंटरफेस के रूप में माने जाने के लिए उसे स्पष्ट रूप से मार्क करना होगा और यह एक एनोटेशन (annotation) का उपयोग करके किया जाता है जिसे हम बाद के सेक्शन में देखेंगे।
अभी के लिए, इसे इस तरह से सोचें:
- trait यह वर्णन करता है कि एक कॉन्ट्रैक्ट में कौन से फंक्शन्स होने चाहिए,
- और एनोटेशन (जिसके बारे में हम जल्द ही बताएंगे) trait को बताता है कि उसे कैसा व्यवहार करना चाहिए, इस मामले में, एक कॉन्ट्रैक्ट इंटरफेस के रूप में।
उन फंक्शन्स को इम्प्लीमेंट करना संभव नहीं है जो परिभाषित इंटरफेस का हिस्सा नहीं हैं - हम बाद में अतिरिक्त फंक्शन्स को इम्प्लीमेंट करने का एक और विकल्प देखेंगे।
नीचे दिया गया कोड एक trait को परिभाषित करके और उसके भीतर घोषित (declared) फंक्शन्स के लिए इम्प्लीमेंटेशन प्रदान करके Counter कॉन्ट्रैक्ट का विस्तार करता है:
// Define a trait with two functions
pub trait ICounter {
fn increase_counter(amount: felt252);
fn get_counter() -> felt252;
}
mod Counter {
// Implement the functions within the `ICounter` trait
impl CounterImpl of super::ICounter {
fn increase_counter(amount: felt252) {
// TODO
}
fn get_counter() -> felt252 {
// TODO
}
}
}
यह ड्राफ्ट निम्नलिखित फीचर्स जोड़ता है:
- एक पब्लिक trait, जिसे
pubऔरtraitकीवर्ड्स द्वारा दर्शाया गया है। - एक इम्प्लीमेंटेशन (
impl) ब्लॉक जिसमें फंक्शन इम्प्लीमेंटेशन्स होते हैं। यह ब्लॉकICountertrait को लागू (implement) करता है। trait या इम्प्लीमेंटेशन ब्लॉक का नाम कुछ भी हो सकता है, हालांकि ऐसे वर्णनात्मक (descriptive) नामों का उपयोग करना एक आम प्रथा है जो कॉन्ट्रैक्ट के उद्देश्य को दर्शाते हों। कन्वेंशन के अनुसार, Scarb इंटरफेस के लिएIContractNameऔर पब्लिक फंक्शन्स को परिभाषित करने वाले संबंधित इम्प्लीमेंटेशन के लिएContractNameImplपैटर्न का पालन करता है।
स्टोरेज (storage) जोड़ना
इसके बाद, हमें काउंटर की वैल्यू को स्टोर करने के लिए एक जगह की आवश्यकता है। चूंकि काउंटर वैल्यू को ट्रांजैक्शन्स के बीच लगातार बने (persist) रहने की आवश्यकता होती है, इसलिए इसे कॉन्ट्रैक्ट के स्टेट (state) के हिस्से के रूप में स्टोर किया जाना चाहिए। Cairo में, स्थायी डेटा को Storage नामक एक सिंगल स्ट्रक्ट (struct) के भीतर स्टोर किया जाता है जो सभी स्टेट वेरिएबल्स को एक साथ समूहित करता है।
// Storage traits
use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess};
struct Storage {
counter: felt252,
}
इसमें निम्नलिखित फीचर्स शामिल हैं:
- एक
useस्टेटमेंट जो कॉन्ट्रैक्ट स्टोरेज से पढ़ने और उसमें लिखने के लिए आवश्यक traits को इम्पोर्ट करता है। - कॉन्ट्रैक्ट स्टोरेज के लिए एक नया स्ट्रक्चर, जिसे
structकीवर्ड के साथ परिभाषित किया गया है। स्ट्रक्ट का नामStorageही होना चाहिए। - स्टोरेज के अंदर वास्तविक
counterवेरिएबल।
स्टेट और लॉजिक जोड़ना
अब जब हमारे पास स्टोरेज है, तो हमारे फंक्शन्स को इसके साथ इंटरेक्ट करने के लिए एक तरीके की आवश्यकता है। Cairo में, कॉन्ट्रैक्ट स्टेट अपने आप उपलब्ध नहीं होती है; इसे स्पष्ट रूप से पैरामीटर के रूप में फंक्शन्स को पास किया जाना चाहिए।
इसे सुविधाजनक बनाने के लिए, Cairo एक स्टेट रेफरेंस का उपयोग करता है, जो एक विशिष्ट पैरामीटर है जो कॉन्ट्रैक्ट के स्टोरेज का प्रतिनिधित्व करता है और उस तक पहुंच की अनुमति देता है।
Cairo में स्टेट रेफरेंस को परिभाषित करने के दो तरीके हैं: एक जो स्टोरेज में रीड (read) और राइट (write) का एक्सेस प्रदान करता है, और दूसरा जो केवल रीड-ओनली एक्सेस प्रदान करता है। यहां बताया गया है कि उनका उपयोग कैसे करें:
- रीड और राइट एक्सेस:
refकीवर्ड के साथ एक रेफरेंस वेरिएबल का उपयोग करें। - रीड-ओनली एक्सेस:
@सिंबल के साथ एक स्नैपशॉट वेरिएबल का उपयोग करें। यह Solidity केviewफंक्शन्स के समान है, जहां फंक्शन कॉन्ट्रैक्ट स्टोरेज से पढ़ तो सकता है लेकिन उसे मॉडिफाई (संशोधित) नहीं कर सकता है।
ध्यान दें कि increase_counter फंक्शन कॉन्ट्रैक्ट के स्टेट में रीड और राइट एक्सेस प्राप्त करने के लिए अपने पैरामीटर में ref कीवर्ड का उपयोग करता है, जबकि get_counter फंक्शन रीड-ओनली एक्सेस प्राप्त करने के लिए @ सिंबल का उपयोग करता है, जैसा कि नीचे दिए गए कोड में दिखाया गया है:
pub trait ICounter<TContractState> {
// Function that can read and modify the contract's state
fn increase_counter(ref self: TContractState, amount: felt252);
// Function that can only read from the contract's state
fn get_counter(self: @TContractState) -> felt252;
}
mod Counter {
use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess};
struct Storage {
counter: felt252,
}
impl CounterImpl of super::ICounter<ContractState> {
// Uses `ref self`: gives read and write access to the storage
fn increase_counter(ref self: ContractState, amount: felt252) {
self.counter.write(self.counter.read() + amount);
}
// Uses `@`: gives read-only access to the storage
fn get_counter(self: @ContractState) -> felt252 {
self.counter.read()
}
}
}
कॉन्ट्रैक्ट में अब तक हमारे द्वारा किए गए परिवर्तन हमें कॉन्ट्रैक्ट स्टेट के माध्यम से सीधे स्टोरेज के साथ इंटरेक्ट करने की अनुमति देते हैं। मुख्य परिवर्तन इस प्रकार हैं:
- trait में
TContractStateटाइप पैरामीटर को एक प्लेसहोल्डर के रूप में जोड़ा गया है, न कि एक वास्तविक टाइप के रूप में, ताकि यह किसी विशिष्ट स्टेट लेआउट से बंधे होने के बजाय किसी भी कॉन्ट्रैक्ट स्टेट लेआउट के साथ काम कर सके।pub trait ICounter {अबpub trait ICounter<TContractState> {बन गया है।
- impl ब्लॉक में,
TContractStateप्लेसहोल्डर को वास्तविक कॉन्ट्रैक्ट स्टेट टाइप (ContractState) से बदल दिया गया है:impl CounterImpl of super::ICounter {अबimpl CounterImpl of super::ICounter<ContractState> {बन गया है।
- दोनों फंक्शन्स में स्टेट का एक रेफरेंस जोड़ा गया है। एक के पास स्टोरेज का राइट (write) एक्सेस है और दूसरे के पास केवल रीड (read) एक्सेस है:
fn increase_counter(amount: felt252) {अबfn increase_counter(ref self: ContractState, amount: felt252) {बन गया है।fn get_counter() -> felt252 {अबfn get_counter(self: @ContractState) -> felt252 {बन गया है।
- काउंटर को बढ़ाने और उसे
selfका उपयोग करके पढ़ने का लॉजिक जोड़ा गया है, जोContractStateटाइप का है, और कॉन्ट्रैक्ट के स्टेट का प्रतिनिधित्व करता है:- लॉजिक
self.counter.write(self.counter.read() + amount);जोड़ा गया। - लॉजिक
self.counter.read()जोड़ा गया।
- लॉजिक
एनोटेशन्स के साथ कॉन्ट्रैक्ट को पूरा करना
कॉन्ट्रैक्ट के विभिन्न भाग कैसा व्यवहार करेंगे, यह इंगित करने के लिए Cairo विभिन्न एनोटेशन्स (जिन्हें एट्रीब्यूट्स भी कहा जाता है) का उपयोग करता है। ये एनोटेशन्स ऐसी चीज़ें निर्दिष्ट (specify) करते हैं जैसे:
- कौन सा trait इंटरफेस को परिभाषित करता है,
- कौन सा मॉड्यूल डिप्लॉय करने योग्य कॉन्ट्रैक्ट है,
- कौन सा स्ट्रक्ट स्टोरेज स्ट्रक्ट है,
- और कौन सा इम्प्लीमेंटेशन ब्लॉक फंक्शन्स को बाहरी दुनिया के सामने एक्सपोज़ करता है।
Cairo में प्रत्येक एनोटेशन #[] से शुरू होता है और इसे सीधे उस कोड के ऊपर रखा जाता है जिस पर यह लागू होता है। उदाहरण के लिए, कोड के एक हिस्से पर इस एट्रीब्यूट #[starknet::interface] को रखने से यह पता चलता है कि इसे कॉन्ट्रैक्ट के इंटरफेस के रूप में माना जाना चाहिए।
यहां अपने एनोटेशन्स के साथ पूरा कॉन्ट्रैक्ट दिया गया है:
#[starknet::interface]
pub trait ICounter<TContractState> {
fn increase_counter(ref self: TContractState, amount: felt252);
fn get_counter(self: @TContractState) -> felt252;
}
#[starknet::contract]
mod Counter {
use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess};
#[storage]
struct Storage {
counter: felt252,
}
#[abi(embed_v0)]
impl CounterImpl of super::ICounter<ContractState> {
fn increase_counter(ref self: ContractState, amount: felt252) {
self.counter.write(self.counter.read() + amount);
}
fn get_counter(self: @ContractState) -> felt252 {
self.counter.read()
}
}
}
जोड़े गए एनोटेशन्स हैं:
#[starknet::interface]एक trait को इंटरफेस के रूप में मार्क करता है। आप एक एनोटेटेड इंटरफेस के बिना एकimplब्लॉक नहीं रख सकते।#[starknet::contract]एक मॉड्यूल को Starknet स्मार्ट कॉन्ट्रैक्ट के रूप में मार्क करता है।#[storage]उस स्ट्रक्चर को इंगित करता है जो कॉन्ट्रैक्ट के स्टोरेज लेआउट को परिभाषित करता है। एक कॉन्ट्रैक्ट में इस एनोटेशन के साथ ठीक एक स्टोरेजstructहोना ही चाहिए।#[abi(embed_v0)],implब्लॉक के अंदर के फंक्शन्स को कॉन्ट्रैक्ट के पब्लिक ABI का हिस्सा बनाता है — जैसे Solidity मेंpublicयाexternalफंक्शन्स। इस एनोटेशन को छोड़ने से फंक्शन्स केवल इसी कॉन्ट्रैक्ट के अंदर उपलब्ध होते हैं।embed_v0का अर्थ है ABI को ABI फॉर्मेट के वर्ज़न 0 का उपयोग करके एम्बेड (embed) करना।v0में, फंक्शन सिलेक्टर्स (selectors) केवल फंक्शन के नाम से प्राप्त होते हैं।implनाम पर विचार नहीं किया जाता है, जिसका अर्थ है कि यदि एक कॉन्ट्रैक्ट में अलग-अलगimplब्लॉक्स (अलग-अलग traits को लागू करने वाले) हैं जो दोनों समान नाम वाले एक फंक्शन को परिभाषित करते हैं, तो नेम कोलिजन (name collision) होगा। भविष्य के वर्ज़न (जैसेv1) बैकवर्ड कम्पैटिबिलिटी को तोड़े बिना सिलेक्टर डेरिवेशन में अधिक कॉन्टेक्स्ट को शामिल करके इसमें सुधार कर सकते हैं।- Cairo में, प्राइवेट फंक्शन को दर्शाने के लिए Solidity के
privateयाinternalजैसे कोई विजिबिलिटी कीवर्ड नहीं हैं। प्राइवेट फंक्शन्स बनाने का एक और तरीका यह है कि उन्हें बिना किसी एनोटेशन केimplब्लॉक के बाहर जोड़ दिया जाए। इसका एक उदाहरण इस लेख में बाद में दिखाया गया है।
इनके सेट हो जाने के बाद, कॉन्ट्रैक्ट को अन्य कॉन्ट्रैक्ट्स या क्लाइंट्स से कम्पाइल, डिप्लॉय और कॉल किए जाने के लिए तैयार है।
इंटरफेस के बाहर फंक्शन्स
Cairo में, #[external(v0)] एनोटेशन के साथ मार्क करके इंटरफेस इम्प्लीमेंटेशन के बाहर पब्लिक फंक्शन्स को परिभाषित करना भी संभव है। #[abi(embed_v0)] की तरह ही, #[external(v0)] में v0 का अर्थ है ABI वर्ज़न 0 का उपयोग करना।
एक कॉन्ट्रैक्ट में इंटरफेस और एनोटेटेड एक्सटर्नल फंक्शन्स दोनों का उपयोग करना संभव है। हालांकि, इंटरफेस का उपयोग करने की सिफारिश की जाती है क्योंकि यह आपके कॉन्ट्रैक्ट के साथ इंटरेक्ट करते समय बाहरी कॉन्ट्रैक्ट्स को एक शेयर की गई (shared) परिभाषा पर भरोसा करने की अनुमति देता है।
निम्नलिखित कोड में, हम एक नया फंक्शन increase_counter_by_five जोड़ेंगे जिसे #[external(v0)] के साथ एनोटेट किया गया है। यह फंक्शन बाहर से कॉल करने योग्य (externally callable) है और कॉन्ट्रैक्ट के ABI में शामिल है, भले ही इसे इंटरफेस के माध्यम से परिभाषित न किया गया हो (यह पब्लिक फंक्शन्स की तरह व्यवहार करता है लेकिन बिना किसी इंटरफेस के)।
यह नया फंक्शन एक और नए, प्राइवेट फंक्शन get_five को कॉल करता है। यह फंक्शन केवल इसी कॉन्ट्रैक्ट के अंदर कॉल किया जा सकता है।
#[starknet::interface]
pub trait IHelloStarknet<TContractState> {
fn increase_counter(ref self: TContractState, amount: felt252);
fn get_counter(self: @TContractState) -> felt252;
}
#[starknet::contract]
mod HelloStarknet {
use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess};
#[storage]
struct Storage {
counter: felt252,
}
#[abi(embed_v0)]
impl CounterImpl of super::IHelloStarknet<ContractState> {
fn increase_counter(ref self: ContractState, amount: felt252) {
self.counter.write(self.counter.read() + amount);
}
fn get_counter(self: @ContractState) -> felt252 {
self.counter.read()
}
}
// ********* NEWLY ADDED - START ********* //
#[external(v0)]
fn increase_counter_by_five(ref self: ContractState) {
self.counter.write(self.counter.read() + get_five());
}
fn get_five() -> felt252 {
5
}
// ********* NEWLY ADDED - END ********* //
}
ध्यान दें कि किसी भी
publicयाexternalफंक्शन का पहला पैरामीटर कॉन्ट्रैक्ट के स्टेट का रेफरेंस होना चाहिए।
कॉन्ट्रैक्ट को कम्पाइल करना
यह सुनिश्चित करने के लिए कि हमारा कोड मान्य (valid) है और रन करने के लिए तैयार है, हमें इसे कम्पाइल करना चाहिए।
Cairo कोड के साथ काम करने के लिए एक लोकप्रिय टूल Scarb है — जो एक Cairo पैकेज मैनेजर और बिल्ड सिस्टम है। यदि आपने इसे पहले ही इंस्टॉल नहीं किया है, तो Cairo for Solidity developers लेख में दिए गए निर्देशों का पालन करके इसे इंस्टॉल करें।
एक बार इंस्टॉल हो जाने के बाद, आप निम्नलिखित स्टेप्स के साथ अपना कॉन्ट्रैक्ट प्रोजेक्ट बना और कम्पाइल कर सकते हैं:
scarb new counterरन करके एक नया प्रोजेक्ट इनिशियलाइज़ (Initialize) करें, और संकेत मिलने पर डिफ़ॉल्ट टेस्ट रनर के साथ आगे बढ़ें।- प्रोजेक्ट फोल्डर में नेविगेट करें:
cd counter। src/lib.cairoके कंटेंट्स को हमारे कॉन्ट्रैक्ट से बदलें।- कॉन्ट्रैक्ट को कम्पाइल करें:
scarb build।
यदि आपको Type annotations needed के समान कम्पाइलेशन एरर्स (errors) मिलते हैं, तो सुनिश्चित करें कि आपके Scarb.toml में [dependencies] सेक्शन के तहत starknet = "2.12.0" जोड़ा गया है।
कॉन्ट्रैक्ट की टेस्टिंग (Testing)
Scarb एक नया प्रोजेक्ट इनिशियलाइज़ करने के बाद एक टेस्ट कॉन्ट्रैक्ट भी जनरेट करता है। टेस्ट सीधे Cairo में लिखे जाते हैं और ऑन-चेन डिप्लॉय करने से पहले वास्तविक कॉन्ट्रैक्ट लॉजिक का परीक्षण करने के लिए उन्हें लोकली एक्ज़ीक्यूट किया जाता है।
टेस्ट देखने के लिए, ./tests/test_contract.cairo पर नेविगेट करें। नीचे जनरेट किए गए टेस्ट में क्या हो रहा है, उसका ब्रेक डाउन दिया गया है।
इम्पोर्ट्स (Imports)

-
use starknet::ContractAddress;यह
starknetमॉड्यूल सेContractAddressइम्पोर्ट करता है।ContractAddressटाइप को इम्पोर्ट करता है।- यह कॉन्ट्रैक्ट एड्रेस का Starknet का प्रतिनिधित्व (representation) है और डिप्लॉय किए गए कॉन्ट्रैक्ट्स के साथ इंटरेक्ट करने या उन्हें रेफरेंस करते समय इसकी आवश्यकता होती है।
-
use snforge_std::{declare, ContractClassTrait, DeclareResultTrait};यह टेस्टिंग के दौरान कॉन्ट्रैक्ट्स को डिक्लेयर और डिप्लॉय करने के लिए आवश्यक टूल्स को starknet foundry स्टैण्डर्ड लाइब्रेरी
snforge_stdसे इम्पोर्ट करता है।declare: डिप्लॉयमेंट से पहले टेस्ट एनवायरनमेंट में कॉन्ट्रैक्ट को डिक्लेयर करने के लिए उपयोग किया जाता है। यह नेटवर्क में कॉन्ट्रैक्ट कोड सबमिट करने जैसा है।ContractClassTrait: डिक्लेयर किए गए कॉन्ट्रैक्ट क्लासेस के साथ इंटरेक्ट करने के लिए हेल्पर मेथड्स प्रदान करता है (जैसे कि उन्हें डिप्लॉय करना)।DeclareResultTrait: डिक्लेरेशन रिज़ल्ट पर एक फंक्शन एक्सपोज़ करता है जो कॉन्ट्रैक्ट क्लास को प्राप्त करता है (Solidity में कॉन्ट्रैक्ट के बाइटकोड के बराबर)।
-
use counter::IHelloStarknetSafeDispatcher;औरuse counter::IHelloStarknetSafeDispatcherTrait;यह प्रोजेक्ट के नाम (हमारे मामले में,
counter) से कॉन्ट्रैक्ट इंटरफेस का सेफ वर्ज़न इम्पोर्ट करता है।-
IHelloStarknetSafeDispatcher: सेफ डिस्पैचर कॉन्ट्रैक्ट के फंक्शन्स को कॉल करने के लिए जिम्मेदार है। लेकिन Solidity के विपरीत जहां एक फंक्शन कॉल सीधे वैल्यू लौटाता है, यहां हर कॉल एक रैपर (wrapper) लौटाता है जिसमें या तो लौटाई गई वैल्यू होती है (यदि सफल हो) या एक एरर होती है (यदि यह विफल हो)।महत्वपूर्ण बात यह है कि, भले ही कोई कॉन्ट्रैक्ट कॉल विफल हो जाए, फिर भी टेस्ट फंक्शन के भीतर एक्ज़ीक्यूशन जारी रहता है। यह सेफ डिस्पैचर को पूरे ट्रांजैक्शन को रिवर्ट (revert) करने के बजाय एरर को अच्छे से (gracefully) हैंडल करने की अनुमति देता है।
-
IHelloStarknetSafeDispatcherTrait: डिस्पैचर के लिए कॉन्ट्रैक्ट में कॉल करने योग्य फंक्शन्स को एक्सपोज़ करता है। प्रत्येक फंक्शन की रिटर्न वैल्यू रैप (wrap) होती है, जो यह दर्शाती है कि यह सफल या विफल हो सकता है।
-
-
use counter::IHelloStarknetDispatcher;औरuse counter::IHelloStarknetDispatcherTrait;यह प्रोजेक्ट नाम (हमारे मामले में,
counter) से कॉन्ट्रैक्ट इंटरफेस (सेफ वर्ज़न नहीं) को इम्पोर्ट करता है।IHelloStarknetDispatcher: डिस्पैचर भी कॉन्ट्रैक्ट के फंक्शन्स को कॉल करता है। हालांकि, सेफ वर्ज़न के विपरीत, यह बिना किसी रैपर के सीधे फंक्शन की वैल्यू लौटाता है। यदि टारगेट कॉन्ट्रैक्ट विफल हो जाता है, तो कॉल तुरंत पैनिक (panic) करता है, जिससे टेस्ट फंक्शन में एक्ज़ीक्यूशन रुक जाता है और किसी भी तरह की ग्रेसफुल एरर हैंडलिंग को रोकता है।IHelloStarknetDispatcherTrait: डिस्पैचर के लिए कॉन्ट्रैक्ट में कॉल करने योग्य फंक्शन्स को एक्सपोज़ करता है। हर फंक्शन इंटरफेस के रॉ (raw) रिटर्न टाइप को लौटाता है।
डिप्लॉय फंक्शन (Deploy function)

यह फंक्शन कॉन्ट्रैक्ट का नाम (हमारे मामले में, HelloStarknet) एक आर्गुमेंट के रूप में लेता है, कॉन्ट्रैक्ट को डिप्लॉय करता है, और इसका कॉन्ट्रैक्ट एड्रेस लौटाता है।
नोट: कॉन्ट्रैक्ट नाम वह आइडेंटिफायर है जो lib.cairo फाइल के अंदर mod कीवर्ड के बाद आता है (mod HelloStarknet), जबकि प्रोजेक्ट का नाम (जैसे counter) बस उस फोल्डर का नाम है जो Scarb के साथ प्रोजेक्ट को इनिशियलाइज़ करते समय बनाया जाता है।
नीचे फंक्शन में क्या हो रहा है उसका एक ब्रेकडाउन है:
declare(name)- यह कॉन्ट्रैक्ट का नाम लेता है (आमतौर पर बाइट एरे (byte array) के रूप में प्रदान किया जाता है) और इसे Starknet नेटवर्क पर डिक्लेयर करता है।
.contract_class()- डिक्लेयर किए गए कॉन्ट्रैक्ट से कॉन्ट्रैक्ट क्लास को निकालता है।
.deploy(@ArrayTrait::new())- कॉन्ट्रैक्ट क्लास को डिप्लॉय करता है।
ArrayTrait::new()का उपयोग कंस्ट्रक्टर आर्गुमेंट्स को पास करने के लिए किया जाता है (यहां यह एक खाली (empty) एरे है क्योंकि कंस्ट्रक्टर कोई पैरामीटर नहीं लेता है)।- यह एक टपल (tuple) लौटाता है जहां पहला एलिमेंट कॉन्ट्रैक्ट का एड्रेस होता है।
- रिटर्न वैल्यू (Return Value)
- यह फंक्शन नए डिप्लॉय किए गए कॉन्ट्रैक्ट का एड्रेस लौटाता है।
टेस्ट केसेज़ (Test cases)

ऊपर दिए गए स्क्रीनशॉट में, दो टेस्ट केसेज़ हैं:
test_increase_balance: कॉन्ट्रैक्ट में फंक्शन्स को कॉल करने के लिए रेगुलर डिस्पैचर का उपयोग करता है।test_cannot_increase_balance_with_zero_value: कॉन्ट्रैक्ट में फंक्शन्स को कॉल करने के लिए सेफ डिस्पैचर का उपयोग करता है।
टेस्ट कमांड (Test command)
टेस्ट करने के लिए निम्नलिखित कमांड रन करें:
scarb test
प्रमुख अंतर और समानताओं का सारांश
इस लेख में, हमने Cairo और Solidity के बीच कई समानताओं को सूचीबद्ध किया है, लेकिन कई अंतर भी बताए हैं। स्पष्टता के लिए, तुलनाएँ इस प्रकार हैं:
- Cairo का
modकीवर्ड Solidity केcontractकीवर्ड के समान ही भूमिका निभाता है। - Cairo के इंटरफेस को
#[starknet::interface]के साथ एनोटेटेड trait का उपयोग करके परिभाषित किया जाता है, जैसे Solidity काinterface। - Cairo में रीड-ओनली फंक्शन्स बनाने के लिए, जैसे Solidity में
view,@सिंबल का उपयोग करके स्टेट को एक स्नैपशॉट के रूप में पास करें। - Cairo में Solidity के
pureजैसे फंक्शन्स बनाने के लिए, फंक्शन को वैसे ही परिभाषित करें जैसे हमनेget_fiveफंक्शन के साथ किया था। - किसी फंक्शन को बाहर से कॉल करने योग्य (externally callable) बनाने के लिए, जैसे Solidity में
publicऔरexternalके साथ,#[external(v0)]का उपयोग करें या इसे#[abi(embed_v0)]के साथ एकimplब्लॉक में इम्प्लीमेंट (लागू) करें।
निष्कर्ष
Solidity और Cairo कॉन्ट्रैक्ट्स बहुत ही समान उद्देश्यों की पूर्ति करते हैं। हालांकि Cairo का सिंटैक्स अलग है, लेकिन कई मुख्य कॉन्सेप्ट्स (अवधारणाएं) Solidity डेवलपर्स को परिचित लगेंगी।
इस लेख में चर्चा किया गया स्ट्रक्चर एक संभावित दृष्टिकोण (approach) है, लेकिन यह Starknet द्वारा प्रदान किया जाने वाला एकमात्र आर्किटेक्चरल विकल्प नहीं है। इस सीरीज़ के आगामी लेखों में, हम वैकल्पिक डिज़ाइनों का पता लगाएंगे ताकि आपको उस लचीलेपन (flexibility) को बेहतर ढंग से समझने में मदद मिल सके जो Cairo और Starknet स्केलेबल और कंपोज़ेबल (composable) स्मार्ट कॉन्ट्रैक्ट्स बनाने के लिए प्रदान करते हैं।
अगले कदम
Cairo कॉन्ट्रैक्ट्स के बारे में सीखना जारी रखने के लिए, आपको हमारे GitHub repo में दिए गए अभ्यासों (exercises) को आज़माने और उनके साथ प्रयोग करने के लिए प्रोत्साहित किया जाता है।
यह लेख Cairo Programming on Starknet पर एक ट्यूटोरियल सीरीज़ का हिस्सा है।