एक constructor एक one-time-call फ़ंक्शन है जिसे कॉन्ट्रैक्ट डिप्लॉयमेंट (contract deployment) के दौरान state variables को इनिशियलाइज़ (initialize) करने, कॉन्ट्रैक्ट सेटअप टास्क करने, क्रॉस-कॉन्ट्रैक्ट इंटरैक्शन करने आदि के लिए एक्सीक्यूट किया जाता है।
Cairo में, constructors को एक कॉन्ट्रैक्ट के mod ब्लॉक के अंदर #[constructor] एट्रिब्यूट का उपयोग करके परिभाषित किया जाता है।
यह लेख कवर करेगा कि Cairo में constructors कैसे काम करते हैं, Scarb टेस्ट के अंदर constructor आर्गुमेंट्स का मैन्युअल और ऑटोमैटिक सीरियलाइज़ेशन (serialization), और constructor की रिटर्न वैल्यूज़ Solidity से कैसे अलग हैं।
एक साधारण Cairo Constructor
आइए एक साधारण Solidity कॉन्ट्रैक्ट लें जो अपने constructor में एक state variable count को इनिशियलाइज़ करता है:
contract Counter {
uint256 public count;
constructor(uint256 _count) {
count = _count;
}
}
यहाँ इसके समान (equivalent) Cairo वर्ज़न है:
#[starknet::interface]
pub trait IHelloStarknet<TContractState> {
fn get_count(self: @TContractState) -> felt252;
}
#[starknet::contract]
mod HelloStarknet {
use starknet::storage::{StoragePointerReadAccess,StoragePointerWriteAccess};
#[storage]
struct Storage {
count: felt252
}
// ************ CONSTRUCTOR FUNCTION ************* //
#[constructor]
fn constructor(ref self: ContractState, _count: felt252) {
self.count.write(_count);
}
#[abi(embed_v0)]
impl HelloStarknetImpl of super::IHelloStarknet<ContractState> {
fn get_count(self: @ContractState) -> felt252 {
self.count.read()
}
}
}
उपरोक्त कोड में #[constructor] एट्रिब्यूट फ़ंक्शन को कॉन्ट्रैक्ट के constructor के रूप में मार्क करता है। फ़ंक्शन का नाम constructor होना चाहिए और यह डिप्लॉयमेंट के दौरान एक बार एक्सीक्यूट होता है। कॉन्ट्रैक्ट के स्टोरेज में राइट एक्सेस (write access) देने के लिए यह ref self लेता है, साथ ही इनिशियलाइज़ेशन के लिए आवश्यक कोई भी पैरामीटर लेता है, इस मामले में, _count।
उपरोक्त constructor केवल आर्गुमेंट के रूप में पास की गई वैल्यू के साथ count स्टोरेज वेरिएबल को इनिशियलाइज़ करता है।
आइए इसका टेस्ट करें, Scarb के साथ एक नया प्रोजेक्ट बनाएँ:
scarb new counter
इसके बाद, src/lib.cairo फ़ाइल में जनरेट किए गए कोड को ऊपर दिए गए HelloStarknet कॉन्ट्रैक्ट कोड से बदलें।
यह वेरिफाई करने के लिए कि count सही ढंग से इनिशियलाइज़ किया गया है, हम एक टेस्ट लिखेंगे जो कॉन्ट्रैक्ट को एक विशिष्ट वैल्यू के साथ डिप्लॉय करता है और फिर यह सुनिश्चित (assert) करता है कि स्टोर की गई वैल्यू हमारे द्वारा पास की गई वैल्यू से मेल खाती है।
टेस्ट फ़ाइल (tests/test_contract.cairo) खोलें, फिर जनरेट किए गए कोड को नीचे दिए गए कोड से बदलें:
use counter::{IHelloStarknetDispatcher, IHelloStarknetDispatcherTrait};
use snforge_std::{ContractClassTrait, DeclareResultTrait, declare};
use starknet::ContractAddress;
fn deploy_contract(name: ByteArray) -> ContractAddress {
let contract = declare(name).unwrap().contract_class();
// CREATE ARGUMENT ARRAY FOR CONSTRUCTOR. WE'RE PASSING `5` AS THE INITIAL VALUE FOR `count`
let mut args = ArrayTrait::new();
args.append(5_felt252);
// DEPLOY THE CONTRACT WITH THE PROVIDED CONSTRUCTOR ARGUMENTS
let (contract_address, _) = contract.deploy(@args).unwrap();
contract_address // Return address of deployed contract
}
#[test]
fn test_count() {
let contract_address = deploy_contract("HelloStarknet");
let dispatcher = IHelloStarknetDispatcher { contract_address };
// CALL THE `get_count` FUNCTION TO READ THE CURRENT VALUE OF `count`
let result = dispatcher.get_count();
// ASSERT THAT THE INITIALIZED VALUE MATCHES WHAT WE PASSED DURING DEPLOYMENT
assert!(result == 5, "failed {} != 5", result);
}
इस टेस्ट का मुख्य हिस्सा यह है कि हम डिप्लॉयमेंट के दौरान constructor आर्गुमेंट को felt252 वैल्यूज़ के एक array के रूप में कैसे पास करते हैं। यह हिस्सा आसानी से छूट सकता है, लेकिन यह महत्वपूर्ण है; हम deploy को कॉल करने से पहले args array में 5 वैल्यू को अपेंड (append) करते हैं, इस तरह हम कॉन्ट्रैक्ट को 5 के साथ इनिशियलाइज़ करते हैं।
// CREATE ARGUMENT ARRAY FOR CONSTRUCTOR. WE'RE PASSING `5` AS THE INITIAL VALUE FOR `count`
let mut args = ArrayTrait::new();
args.append(5_felt252);
// DEPLOY THE CONTRACT WITH THE PROVIDED CONSTRUCTOR ARGUMENTS
let (contract_address, _) = contract.deploy(@args).unwrap();
टेस्ट का बाकी हिस्सा यह पुष्टि करता है कि यह इनिशियलाइज़ेशन उम्मीद के मुताबिक काम कर रहा है। हम get_count को कॉल करते हैं, जो count वेरिएबल की वर्तमान वैल्यू देता है, और फिर यह सुनिश्चित करते हैं कि यह 5 के बराबर है।
Solidity के foundry टेस्ट के विपरीत, जहाँ constructor आर्गुमेंट्स विभिन्न प्रकार (integers, strings, arrays, structs, addresses आदि) के हो सकते हैं, Scarb टेस्ट्स के अंदर कॉन्ट्रैक्ट डिप्लॉयमेंट के लिए सभी constructor आर्गुमेंट्स को felt252 वैल्यूज़ के रूप में पास करना आवश्यक है। ऐसा इसलिए है क्योंकि Starknet VM एक felt252-आधारित सिस्टम है, हुड के नीचे (under the hood) सब कुछ felts के रूप में एनकोड किया जाता है और पास किया जाता है।
हालाँकि, हमें टेस्ट में डिप्लॉयमेंट के दौरान हमेशा हर आर्गुमेंट को मैन्युअल रूप से felts में कन्वर्ट करने की आवश्यकता नहीं होती है। Starknet Foundry एक हेल्पर फ़ंक्शन (helper function) प्रदान करता है जो टेस्ट्स में constructor-argument सीरियलाइज़ेशन और कॉन्ट्रैक्ट डिप्लॉयमेंट को स्वचालित रूप से (automatically) हैंडल करता है। हम इसे जल्द ही कवर करेंगे लेकिन हेल्पर का उपयोग करने से पहले, हम सीखेंगे कि प्रिमिटिव (primitive) और कॉम्प्लेक्स (complex) प्रकारों को कैसे सीरियलाइज़ किया जाए ताकि हमें पता चले कि हेल्पर परदे के पीछे क्या कर रहा है।
Constructor में felt252 के अलावा अन्य Primitive Types पास करना
जैसा कि पहले उल्लेख किया गया है, हम डिप्लॉयमेंट के दौरान non-felt252 वैल्यूज़ (जैसे ContractAddress) को felt252 में मैन्युअल रूप से सीरियलाइज़ कर सकते हैं, उन्हें felt252 के एक array के रूप में पास कर सकते हैं, और फिर कॉन्ट्रैक्ट की स्टेट को इनिशियलाइज़ करने के लिए उन्हें constructor में डिकोड कर सकते हैं। आइए यह समझने के लिए एक उदाहरण देखें कि यह व्यवहार में कैसे काम करता है।
Solidity वर्ज़न:
contract SomeContract {
uint256 count;
address owner;
bool isActive;
constructor(uint256 _count, address _owner, bool _isActive) {
count = _count;
owner = _owner;
isActive = _isActive;
}
}
यहाँ Cairo में इसके समान वर्ज़न है:
#[starknet::contract]
mod HelloStarknet {
use starknet::ContractAddress;
use starknet::storage::{StoragePointerWriteAccess};
// Define the contract's storage.
#[storage]
struct Storage {
count: u256,
owner: ContractAddress,
is_active: bool,
}
// CONSTRUCTOR FUNCTION
#[constructor]
fn constructor(
ref self: ContractState,
_count: u256,
_owner: ContractAddress,
_is_active: bool
) {
// INIT STATE VARS
self.count.write(_count);
self.owner.write(_owner);
self.is_active.write(_is_active);
}
}
यहाँ constructor तीन आर्गुमेंट्स स्वीकार करता है:
_count: 256-bit की काउंट (count) वैल्यू।_owner: कॉन्ट्रैक्ट ओनर (contract owner) का एड्रेस।_is_active: एक बूलियन फ़्लैग (boolean flag) जो यह दर्शाता है कि कॉन्ट्रैक्ट को एक्टिव (active) स्टेट में शुरू होना चाहिए या नहीं।
फिर .write() मेथड का उपयोग करके प्रदान किए गए प्रत्येक आर्गुमेंट को उनके संबंधित (corresponding) स्टोरेज वेरिएबल्स में लिखता है, जिससे डिप्लॉयमेंट पर स्टेट्स इनिशियलाइज़ हो जाती हैं।
डिप्लॉयमेंट से पहले Constructor Arguments को मैन्युअल रूप से सीरियलाइज़ करना
टेस्ट में, नीचे दिया गया deploy_contract फ़ंक्शन दिखाता है कि जो वैल्यूज़ felt252 प्रकार की नहीं हैं, उन्हें constructor आर्गुमेंट्स के रूप में पास किए जाने से पहले “मैन्युअल रूप से” कैसे सीरियलाइज़ किया जाता है:
fn deploy_contract(name: ByteArray) -> ContractAddress {
let contract = declare(name).unwrap().contract_class();
// CREATE ARGUMENT ARRAY FOR CONSTRUCTOR
let mut args = ArrayTrait::new();
// VALUES TO SERIALIZE
let count: u256 = 115792089237316195423570985008687907853269984665640564039457584007913129639935; // count = max value of u256
let owner: ContractAddress = 0xbeef.try_into().unwrap();
let is_active: bool = true;
// Serialize the u256 `count` value into two felt252 elements (low, high)
// and push them into the constructor `args` array.
count.serialize(ref args);
// SERIALIZE INTO FELT252, THEN PUSH TO `args` ARRAY
owner.serialize(ref args); // ContractAddress -> felt252
is_active.serialize(ref args); // bool -> felt252
// DEPLOY THE CONTRACT WITH THE PROVIDED CONSTRUCTOR ARGUMENTS
let (contract_address, _) = contract.deploy(@args).unwrap();
contract_address
}
Starknet में कई constructor आर्गुमेंट्स के साथ काम करते समय, उन्हें एक Array<felt252> के रूप में पास किया जाना चाहिए।
इस उदाहरण में, क्योंकि count वेरिएबल u256 प्रकार का है, .serialize() मेथड args array में अपेंड होने से पहले इसकी वैल्यू को दो 128-बिट हिस्सों; low और high में विभाजित (split) कर देता है। जैसा कि हमने पहले कहा था, ऐसा इसलिए है क्योंकि एक अकेला felt252 सुरक्षित रूप से पूरे 256-बिट इंटीजर (integer) को नहीं रख सकता है। वैल्यूज़ को ठीक से सीरियलाइज़ करके (प्रत्येक आधे को एक अलग felt252 के रूप में एनकोड करना), constructor फ़ंक्शन स्वचालित रूप से उन्हें मूल (original) u256 वैल्यू में डीसीरियलाइज़ (deserialize) कर सकता है।
शेष constructor वैल्यूज़; owner (एक ContractAddress) और is_active (एक bool) प्रत्येक एक सिंगल felt252 में फिट हो जाते हैं, इसलिए उन्हें सीरियलाइज़ करना सीधा है। उन्हें ठीक उसी क्रम (order) में सीरियलाइज़ करना महत्वपूर्ण है जो constructor के पैरामीटर अनुक्रम (sequence) से मेल खाता है क्योंकि यदि आर्गुमेंट्स constructor के अपेक्षित (expected) पैरामीटर क्रम के साथ अलाइन या मेल नहीं खाते हैं तो डिप्लॉयमेंट विफल हो जाएगा या अप्रत्याशित व्यवहार उत्पन्न करेगा।
अंतिम चरण में array को contract.deploy(@args) में पास करते समय @ ऑपरेटर का उपयोग किया जाता है, जो स्वामित्व (ownership) ट्रांसफर किए बिना array डेटा पास करने का Starknet का तरीका है।
अगले भाग में, हम देखेंगे कि एक हेल्पर फ़ंक्शन का उपयोग करके टेस्ट्स में constructor आर्गुमेंट सीरियलाइज़ेशन को कैसे स्वचालित किया जाए जो हमारे लिए उपरोक्त सभी सीरियलाइज़ेशन स्टेप्स करता है।
Complex Type पास करना
प्रिमिटिव प्रकारों की तरह ही, जटिल (complex) constructor आर्गुमेंट्स को भी डिप्लॉयमेंट से पहले felt252 वैल्यूज़ के एक array में सीरियलाइज़ किया जाना चाहिए।
आइए नीचे दिए गए इस Solidity कॉन्ट्रैक्ट पर विचार करें जो constructor के माध्यम से एक कॉम्प्लेक्स टाइप (एक struct) को इनिशियलाइज़ करता है:
// SPDX-License-Identifier: MIT
pragma solidity =0.8.30;
contract Bank {
struct Account {
address wallet;
uint64 balance;
}
Account userAccount;
constructor(Account memory _userAccount) {
userAccount = _userAccount;
}
}
Solidity में यह काफी सीधा (straightforward) है।
नीचे Cairo में इसके समान वर्ज़न है:
#[starknet::contract]
pub mod HelloStarknet {
use starknet::ContractAddress;
use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess};
// `Account` STRUCT
#[derive(Drop, starknet::Store)]
pub struct Account {
pub wallet: ContractAddress,
pub balance: u64,
}
#[storage]
struct Storage {
// Use the `Account` struct in storage
pub user_account: Account,
}
// Constructor function
#[constructor]
// Each field is declared as its own constructor argument
fn constructor(
ref self: ContractState,
new_user_account: Account,
) {
// WRITE `new_user_account` STRUCT TO STORAGE
self.user_account.write(new_user_account);
}
}
यहाँ, कॉन्ट्रैक्ट मॉड्यूल को pub mod के रूप में मार्क किया गया है, जो इसे एक पब्लिक मॉड्यूल (public module) बनाता है। यह बाहरी कोड, जैसे कि टेस्ट्स या अन्य मॉड्यूल्स, को इसके अंदर परिभाषित आइटम्स (जैसे Account struct) तक पहुँचने की अनुमति देता है। चूँकि Account को भी pub के रूप में घोषित किया गया है, यह कॉन्ट्रैक्ट मॉड्यूल के बाहर पूरी तरह से एक्सेसिबल हो जाता है, जो टेस्ट में इस struct को सीरियलाइज़ करने के लिए आवश्यक है।
Account struct पर डिराइव (derive) किया गया starknet::Store trait ही Cairo को स्टोरेज के लिए struct को सही ढंग से सीरियलाइज़ और डीसीरियलाइज़ करने की अनुमति देता है। इस trait के बिना, कंपाइलर को यह नहीं पता होगा कि स्टोरेज में लिखते या पढ़ते समय struct को कैसे हैंडल करना है।
अंत में, constructor एक सिंगल पैरामीटर लेता है, Account प्रकार का new_user_account, और इसे सीधे स्टोरेज में लिखता है।
deploy_for_test के साथ Automatic Constructor Argument Serialization और Deployment
यह मेथड Starknet Foundry द्वारा प्रदान किए गए एक हेल्पर फ़ंक्शन deploy_for_test का उपयोग करके हमारे लिए सभी भारी काम (heavy lifting) को संभालता है, जो डिप्लॉयमेंट से पहले constructor इनपुट्स को स्वचालित रूप से सीरियलाइज़ करता है।
इस एप्रोच (approach) के साथ, मैन्युअल रूप से Array<felt252> बनाने या प्रत्येक आर्गुमेंट पर .serialize() कॉल करने की आवश्यकता नहीं है। इसके बजाय, हेल्पर फ़ंक्शन कॉन्ट्रैक्ट के constructor सिग्नेचर (signature) को पढ़ता है और पर्दे के पीछे सही एनकोडिंग करता है, यह सुनिश्चित करते हुए कि हर पैरामीटर ठीक उसी तरह सीरियलाइज़ किया गया है जैसा कॉन्ट्रैक्ट अपेक्षा करता है।
deploy_for_test फ़ंक्शन सिग्नेचर:

deploy_for_test फ़ंक्शन पैरामीटर्स
लाल बॉक्स में, deploy_for_test के पैरामीटर्स हैं। पहले दो हैं:
class_hash: कॉन्ट्रैक्ट का कंपाइल किया गया क्लास हैश (class hash)deployment_params: एक स्ट्रक्चर जिसमें एक नया कॉन्ट्रैक्ट इंस्टेंस (contract instance) डिप्लॉय करने के लिए आवश्यक फ़ील्ड्स होते हैं
इन दो निश्चित (fixed) पैरामीटर्स के बाद, फ़ंक्शन उतने ही constructor आर्गुमेंट्स लेता है जितने कॉन्ट्रैक्ट परिभाषित करता है:
<constructor_param1>: <constructor_param_type1><constructor_param2>: <constructor_param_type2>- …
<constructor_paramN>: <constructor_param_typeN>
दूसरे शब्दों में, पैरामीटर 1 से N सीधे कॉन्ट्रैक्ट के constructor पैरामीटर्स के साथ मैप होते हैं। उदाहरण के लिए, इस तरह के constructor को देखते हुए:
#[constructor]
fn constructor(
ref self: ContractState,
count: u256,
owner: ContractAddress,
is_active: bool
) {
...
}
deploy_for_test कॉल इस तरह दिखेगी:
deploy_for_test(
// **** First two params - START **** //
class_hash,
deployment_params,
// **** First two params - END **** //
// **** Constructor params - START **** //
count,
owner,
is_active,
// **** Constructor params - END **** //
);
deploy_for_test फ़ंक्शन रिटर्न टाइप

नीले बॉक्स में रिटर्न टाइप है। फ़ंक्शन एक Result देता है, जो दो परिणामों में से एक हो सकता है:
-
Ok(..): जिसका अर्थ है कि डिप्लॉयमेंट सफल रहा।यह एक टपल (tuple) देता है जिसमें शामिल हैं:
- नए डिप्लॉय किए गए कॉन्ट्रैक्ट का
ContractAddress, और - एक
Span<felt252>जो constructor द्वारा लौटाई गई किसी भी वैल्यू को दर्शाता है (इसे इस अध्याय में बाद में कवर किया गया है)।
- नए डिप्लॉय किए गए कॉन्ट्रैक्ट का
-
Err(..): जिसका अर्थ है कि डिप्लॉयमेंट विफल हो गया।इस स्थिति में, फ़ंक्शन एक
Array<felt252>देता है जिसमें विफल डिप्लॉयमेंट द्वारा एमिट (emit) किया गया एरर (error) डेटा होता है।
व्यावहारिक उदाहरण (Practical Example)
इस फ़ंक्शन को व्यवहार में उपयोग करने के लिए, आइए पहले दिखाए गए कॉन्ट्रैक्ट को डिप्लॉय करें, जिसके constructor में एक सिंगल Account पैरामीटर होता है। चूँकि मॉड्यूल (कॉन्ट्रैक्ट) और Account struct दोनों को pub के रूप में मार्क किया गया था, इसलिए टेस्ट एनवायरनमेंट उन्हें स्वचालित रूप से आयात (import) और सीरियलाइज़ कर सकता है।
नीचे एक पूर्ण उदाहरण दिया गया है जो यह दर्शाता है कि कॉन्ट्रैक्ट को कैसे डिक्लेयर किया जाए, डिप्लॉयमेंट पैरामीटर्स तैयार किए जाएँ, Account आर्गुमेंट को कैसे कंस्ट्रक्ट (construct) किया जाए, और अंत में deploy_for_test का उपयोग करके कॉन्ट्रैक्ट को कैसे डिप्लॉय किया जाए:
// ********* NEW IMPORTS - START ********* //
use myconstructor::HelloStarknet::{Account, deploy_for_test};
use starknet::deployment::DeploymentParams;
// ********** NEW IMPORTS - END ********** //
use myconstructor::{IHelloStarknetDispatcher, IHelloStarknetDispatcherTrait};
use snforge_std::{DeclareResult, DeclareResultTrait, declare};
use starknet::ContractAddress;
fn deploy_contract(name: ByteArray) -> ContractAddress {
// 1. Declare contract to get the class_hash
let declare_result: DeclareResult = declare(name).unwrap();
let class_hash = declare_result.contract_class().class_hash;
// 2. Create deployment parameters
let deployment_params = DeploymentParams { salt: 0, deploy_from_zero: true };
// 3. Create new account
let new_account = Account { wallet: 'BOB'.try_into().unwrap(), balance: 5 };
// 4. Use `deploy_for_test` to deploy the contract
// It automatically handles serialization of constructor parameters
let (_contract_address, _) = deploy_for_test(*class_hash, deployment_params, new_account)
.expect('Deployment failed');
_contract_address
}
-
use myconstructor::HelloStarknet::{Account, deploy_for_test};यहाँ,
myconstructorप्रोजेक्ट का नाम है, औरHelloStarknetउस प्रोजेक्ट के अंदर परिभाषित कॉन्ट्रैक्ट मॉड्यूल है। HelloStarknet कॉन्ट्रैक्ट मॉड्यूल को आयात करके, हमारे पासAccountstruct औरdeploy_for_testहेल्पर फ़ंक्शन का एक्सेस है। -
use starknet::deployment::DeploymentParams;यह इम्पोर्ट
DeploymentParamsलाता है, जो Starknet कोर लाइब्रेरी द्वारा प्रदान किया गया एक struct है। यह कॉन्फ़िगरेशन की अनुमति देता है कि कॉन्ट्रैक्ट को कैसे डिप्लॉय किया जाना चाहिए (उदा. कस्टम साल्ट (custom salt) का उपयोग करना या ज़ीरो से डिप्लॉय करना)।deploy_for_testको कॉल करते समय यह हमेशा आवश्यक होता है, क्योंकि वह फ़ंक्शन अपने दूसरे आर्गुमेंट के रूप में डिप्लॉयमेंट पैरामीटर्स की अपेक्षा करता है।
अंत में, deploy_contract फ़ंक्शन सब कुछ एक साथ लाता है। यह पहले कॉन्ट्रैक्ट को उसका class_hash प्राप्त करने के लिए डिक्लेयर करता है, फिर आवश्यक DeploymentParams तैयार करता है, एक नया Account struct बनाता है जिसे कॉन्ट्रैक्ट के constructor को पास किया जाएगा, और अंत में deploy_for_test को कॉल करता है।
Exercise: the Cairo-Exercises रेपो (repo) में constructor एक्सरसाइज़ को सॉल्व करें।
Constructors में Return Values
Solidity में, एक constructor कभी भी वैल्यू रिटर्न नहीं करता है। डिप्लॉयमेंट के दौरान, EVM constructor को एक्सीक्यूट करता है और इसके एकमात्र “आउटपुट” को ऑन-चेन (on-chain) स्टोर करने के लिए रनटाइम बाइटकोड (runtime bytecode) के रूप में मानता है।
दूसरी ओर Cairo अलग तरह से काम करता है। डिप्लॉयमेंट के बाद, यह एक टपल देता है: (ContractAddress, Span<felt252>)।
ContractAddress: डिप्लॉय किए गए कॉन्ट्रैक्ट का एड्रेस।Span<felt252>:felt252वैल्यूज़ का एक स्पैन (span) जो constructor का रिटर्न डेटा रखता है।felt252के अलावा किसी भी अन्य प्रकार को यहाँ रखे जाने से पहले स्वचालित रूप से कन्वर्ट कर दिया जाता है।
इसे दर्शाने के लिए, आइए एक नया scarb प्रोजेक्ट बूटस्ट्रैप (bootstrap) करें:
scarb new rett
फिर हम lib.cairo में अपने जनरेट किए गए कॉन्ट्रैक्ट में एक constructor जोड़ते हैं, कुछ इस तरह:
#[starknet::interface]
pub trait IHelloStarknet<TContractState> {
fn increase_balance(ref self: TContractState, amount: felt252);
fn get_balance(self: @TContractState) -> felt252;
}
#[starknet::contract]
mod HelloStarknet {
use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess};
#[storage]
struct Storage {
balance: felt252,
}
// ************************ NEWLY ADDED - START ***********************//
#[constructor]
fn constructor(ref self: ContractState) -> felt252 {
33
}
// ************************ NEWLY ADDED - END ************************//
#[abi(embed_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()
}
}
}
चीजों को सरल रखने के लिए, हमारा constructor केवल 33 वैल्यू रिटर्न करेगा।
यह दिखाने के लिए कि constructor वास्तव में एक वैल्यू रिटर्न करता है, आइए टेस्ट फ़ाइल test_contract.cairo पर जाएँ और जनरेट किए गए कोड को इससे बदलें (पठनीयता (readability) के लिए कोड को सरल बनाया गया है):
use rett::{IHelloStarknetDispatcher, IHelloStarknetDispatcherTrait};
use snforge_std::{ContractClassTrait, DeclareResultTrait, declare};
use starknet::ContractAddress;
fn deploy_contract(name: ByteArray) -> ContractAddress {
let contract = declare(name).unwrap().contract_class();
let (contract_address, _) = contract.deploy(@ArrayTrait::new()).unwrap();
contract_address
}
#[test]
fn test_increase_balance() {
let contract_address = deploy_contract("HelloStarknet");
}
हम इस स्क्रीनशॉट में हाइलाइट किए गए हिस्से में बदलाव करेंगे:

अपडेटेड टेस्ट कोड
जो बदलाव हुए हैं वे हैं:
- रिटर्न टाइप:
deploy_contractफ़ंक्शन अब केवलContractAddressके बजाय एक टपल(ContractAddress, felt252)रिटर्न करता है। - Constructor आउटपुट कैप्चर: constructor की रिटर्न वैल्यूज़ को रखने के लिए
Span<felt252>प्रकार काret_valsपेश (introduce) किया गया। - टपल रिटर्न: हम कॉन्ट्रैक्ट एड्रेस को
ret_valsके पहले एलिमेंट के साथ रिटर्न करते हैं, क्योंकि constructor केवल एक सिंगल वैल्यू रिटर्न करता है।
अंत में, टेस्ट यह सुनिश्चित करता है कि constructor की रिटर्न वैल्यू 33 है, जो पुष्टि करता है कि वैल्यू डिप्लॉयमेंट के दौरान सही ढंग से वापस पास की गई है।
use rett::{IHelloStarknetDispatcher, IHelloStarknetDispatcherTrait};
use snforge_std::{ContractClassTrait, DeclareResultTrait, declare};
use starknet::ContractAddress;
// Change return type to a tuple so we can capture the constructor’s return value.
fn deploy_contract(name: ByteArray) -> (ContractAddress, felt252) {
let contract = declare(name).unwrap().contract_class();
// Capture both the contract address and the constructor’s return values (as a Span<felt252>).
let (contract_address, ret_vals) = contract.deploy(@ArrayTrait::new()).unwrap();
// Return the address plus the first element in ret_vals (we expect only one value).
(contract_address, *ret_vals.at(0))
}
#[test]
fn test_increase_balance() {
let (_, ret_val) = deploy_contract("HelloStarknet");
// Verify that the constructor actually returned 33 as expected.
assert(ret_val == 33, 'Invalid return value.');
}
पुष्टि करने के लिए, scarb test चलाएँ, टेस्ट पास हो जाना चाहिए। बाद के एक लेख में हम देखेंगे कि एक कॉन्ट्रैक्ट को दूसरे से सीधे कैसे डिप्लॉय किया जाए।
Starknet में Payable-जैसे Constructor
हालाँकि STRK एक ERC-20 टोकन की तरह व्यवहार करता है, यह Starknet के नेटिव फी टोकन (native fee token) के रूप में भी कार्य करता है। हालाँकि, Starknet में Ethereum के ETH के समान कोई सच्चा “नेटिव टोकन” नहीं है। परिणामस्वरूप, Cairo “payable” constructors का समर्थन (support) नहीं करता है। यदि हम यह लागू (enforce) करना चाहते हैं कि डिप्लॉयमेंट के समय किसी कॉन्ट्रैक्ट में एक निश्चित STRK बैलेंस हो, तो हम अनुमानित एड्रेस (predicted address) पर STRK ट्रांसफर कर सकते हैं, फिर constructor में यह सुनिश्चित कर सकते हैं कि कॉन्ट्रैक्ट का बैलेंस कम से कम वांछित मात्रा (desired amount) के बराबर हो।
यह लेख Cairo Programming on Starknet पर एक ट्यूटोरियल श्रृंखला (tutorial series) का हिस्सा है।