आज हम सीखेंगे कि एक Solana प्रोग्राम कैसे बनाया जाता है जो नीचे दिए गए Solidity कॉन्ट्रैक्ट के समान ही काम करता है। हम यह भी सीखेंगे कि Solana overflow जैसे arithmetic मुद्दों को कैसे संभालता है।
contract Day2 {
event Result(uint256);
event Who(string, address);
function doSomeMath(uint256 a, uint256 b) public {
uint256 result = a + b;
emit Result(result);
}
function sayHelloToMe() public {
emit Who("Hello World", msg.sender);
}
}
आइए एक नया प्रोजेक्ट शुरू करें
anchor init day2
cd day2
anchor build
anchor keys sync
सुनिश्चित करें कि आपके पास एक टर्मिनल में Solana test validator चल रहा है:
solana-test-validator
और दूसरे में Solana logs:
solana logs
टेस्ट चलाकर सुनिश्चित करें कि नया तैयार किया गया प्रोग्राम ठीक से काम कर रहा है:
anchor test --skip-local-validator
Function Arguments प्रदान करना
कोई भी गणितीय (math) कार्य करने से पहले, आइए initialize फ़ंक्शन को दो इंटिजर्स (integers) प्राप्त करने के लिए बदलें। Ethereum “standard” इंटिजर साइज़ के रूप में uint256 का उपयोग करता है। Solana पर, यह u64 है — जो Solidity में uint64 के बराबर है।
Unsigned integers पास करना
डिफ़ॉल्ट initialize फ़ंक्शन निम्नलिखित जैसा दिखेगा:
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
Ok(())
}
lib.rs में initialize() फ़ंक्शन को निम्नलिखित के अनुसार संशोधित करें।
pub fn initialize(ctx: Context<Initialize>,
a: u64,
b: u64) -> Result<()> {
msg!("You sent {} and {}", a, b);
Ok(())
}
अब हमें ./tests/day2.ts में टेस्ट को बदलना होगा।
it("Is initialized!", async () => {
// Add your test here.
const tx = await program.methods
.initialize(new anchor.BN(777), new anchor.BN(888)).rpc();
console.log("Your transaction signature", tx);
});
अब anchor test --skip-local-validator को फिर से चलाएँ।
जब हम लॉग (logs) में देखते हैं, तो हमें कुछ इस तरह दिखना चाहिए:
Transaction executed in slot 367357:
Signature: 54iJFbtEE61T9X2WCLbMe8Dq2YYBzCLYE4qW2DqTsA4gZRgootcubLgHc1MHYncbP63sxNxEY8tJfgfgsdt1Ch4g
Status: Ok
Log Messages:
Program 8o3ehd3XnyDocd9hG1uz5trbmSRB7gaLaE9BCXDpEnMY invoke [1]
Program log: Instruction: Initialize
Program log: You sent 777 and 888
Program 8o3ehd3XnyDocd9hG1uz5trbmSRB7gaLaE9BCXDpEnMY consumed 1116 of 200000 compute units
Program 8o3ehd3XnyDocd9hG1uz5trbmSRB7gaLaE9BCXDpEnMY success
String पास करना
अब आइए दर्शाते हैं कि एक स्ट्रिंग (string) को आर्ग्यूमेंट (argument) के रूप में कैसे पास किया जाए।
pub fn initialize(ctx: Context<Initialize>,
a: u64,
b: u64,
message: String) -> Result<()> {
msg!("You said {:?}", message);
msg!("You sent {} and {}", a, b);
Ok(())
}
और टेस्ट को बदलें।
it("Is initialized!", async () => {
// Add your test here.
const tx = await program.methods
.initialize(
new anchor.BN(777), new anchor.BN(888), "hello").rpc();
console.log("Your transaction signature", tx);
});
जब हम टेस्ट चलाते हैं, तो हमें नया लॉग दिखाई देता है।
Numbers का Array
आगे हम संख्याओं के array को पास करने का उदाहरण देने के लिए एक फ़ंक्शन (और टेस्ट) जोड़ते हैं। Rust में, “vector”, या Vec वह है जिसे Solidity “array” कहता है।
pub fn initialize(ctx: Context<Initialize>,
a: u64,
b: u64,
message: String) -> Result<()> {
msg!("You said {:?}", message);
msg!("You sent {} and {}", a, b);
Ok(())
}
// added this function
pub fn array(ctx: Context<Initialize>,
arr: Vec<u64>) -> Result<()> {
msg!("Your array {:?}", arr);
Ok(())
}
और हम यूनिट टेस्ट को इस प्रकार अपडेट करते हैं:
it("Is initialized!", async () => {
// Add your test here.
const tx = await program.methods.initialize(new anchor.BN(777), new anchor.BN(888), "hello").rpc();
console.log("Your transaction signature", tx);
});
// added this test
it("Array test", async () => {
const tx = await program.methods.array([new anchor.BN(777), new anchor.BN(888)]).rpc();
console.log("Your transaction signature", tx);
});
और हम फिर से टेस्ट चलाते हैं और array आउटपुट देखने के लिए लॉग (logs) देखते हैं:
Transaction executed in slot 368489:
Signature: 3TBzE3NddEY8KREv1FSXnieoyT6G6iNxF1n4hJHCeeWhAsUward3MEKm9WJHV4PMjPxeN2jRSRC9Rq8FUKjXoBQR
Status: Ok
Log Messages:
Program 8o3ehd3XnyDocd9hG1uz5trbmSRB7gaLaE9BCXDpEnMY invoke [1]
Program log: Instruction: Initialize
Program log: You said [777, 888]
Program 8o3ehd3XnyDocd9hG1uz5trbmSRB7gaLaE9BCXDpEnMY consumed 1587 of 200000 compute units
Program 8o3ehd3XnyDocd9hG1uz5trbmSRB7gaLaE9BCXDpEnMY success
Tip: यदि आप अपने Anchor टेस्ट्स पर किसी समस्या में फंस गए हैं, तो अपनी त्रुटि (error) के संबंध में “Solana web3 js” के लिए Google करने का प्रयास करें। Anchor द्वारा उपयोग की जाने वाली Typescript लाइब्रेरी Solana Web3 JS लाइब्रेरी है।
Solana में Math
Floating point math
Solana में floating point ऑपरेशन्स के लिए कुछ, हालांकि सीमित, नेटिव सपोर्ट है।
हालाँकि, floating point ऑपरेशन्स से बचना सबसे अच्छा है क्योंकि वे बहुत अधिक computationally intensive होते हैं (हम इसका एक उदाहरण बाद में देखेंगे)। ध्यान दें कि Solidity में floating point ऑपरेशन्स के लिए कोई नेटिव सपोर्ट नहीं है।
फ़्लोट्स (floats) के उपयोग की सीमाओं के बारे में यहाँ और पढ़ें।
Arithmetic Overflow
Solidity में वर्ज़न 0.8.0 के भाषा में डिफ़ॉल्ट रूप से overflow प्रोटेक्शन को शामिल करने तक, Arithmetic overflow एक सामान्य अटैक वेक्टर (attack vector) था। Solidity 0.8.0 या उसके बाद के वर्ज़न में, overflow चेक्स डिफ़ॉल्ट रूप से किए जाते हैं। चूँकि इन चेक्स में गैस (gas) की खपत होती है, इसलिए कभी-कभी डेवेलपर्स उन्हें रणनीतिक रूप से “unchecked” ब्लॉक के साथ डिसेबल कर देते हैं।
Solana arithmetic overflow से कैसे बचाव करता है?
Method 1: Cargo.toml में overflow-checks = true
यदि Cargo.toml फ़ाइल में overflow-checks की (key) true पर सेट है, तो Rust कंपाइलर स्तर पर overflow चेक्स जोड़ देगा। आगे Cargo.toml का स्क्रीनशॉट देखें:

यदि Cargo.toml फ़ाइल को इस तरह से कॉन्फ़िगर किया गया है, तो आपको overflow के बारे में चिंता करने की आवश्यकता नहीं है।
हालाँकि, overflow चेक्स जोड़ने से ट्रांज़ैक्शन की compute cost बढ़ जाती है (हम इस पर जल्द ही वापस आएँगे)। इसलिए कुछ परिस्थितियों में जहाँ compute cost एक समस्या है, आप overflow-checks को false पर सेट करना चाह सकते हैं। रणनीतिक रूप से overflows की जांच करने के लिए, आप Rust में checked_* ऑपरेटरों का उपयोग कर सकते हैं।
Method 2: checked_* ऑपरेटरों का उपयोग करना।
आइए देखें कि Rust के भीतर ही arithmetic ऑपरेशन्स में overflow चेक्स कैसे लागू किए जाते हैं। नीचे दिए गए Rust स्निपेट पर विचार करें।
- लाइन 1 पर, हम सामान्य
+ऑपरेटर का उपयोग करके गणित (arithmetic) करते हैं, जो चुपचाप (silently) overflow हो जाता है। - लाइन 2 पर, हम
.checked_addका उपयोग करते हैं, जो overflow होने पर एक त्रुटि (error) देगा। ध्यान दें कि हमारे पास अन्य ऑपरेशन्स के लिए.checked_*उपलब्ध है, जैसेchecked_subऔरchecked_mul।
let x: u64 = y + z; // will silently overflow
let xSafe: u64 = y.checked_add(z).unwrap(); // will panic if overflow happens
// checked_sub, checked_mul, etc are also available
Exercise 1: overflow-checks = true सेट करें और एक ऐसा टेस्ट केस बनाएँ जहाँ आप 0 - 1 करके एक u64 को underflow करते हैं। आपको उन नंबरों को आर्ग्यूमेंट के रूप में पास करना होगा अन्यथा कोड कंपाइल नहीं होगा। क्या होता है?
जब टेस्ट चलता है तो आप देखेंगे कि ट्रांज़ैक्शन विफल (fail) हो जाता है (नीचे दिखाए गए एक काफी गूढ़/cryptic एरर मैसेज के साथ)। ऐसा इसलिए है क्योंकि Anchor ने overflow प्रोटेक्शन चालू कर दिया है:

Exercise 2: अब overflow-checks को false में बदलें, फिर टेस्ट को फिर से चलाएँ। आपको 18446744073709551615 का underflow वैल्यू दिखाई देना चाहिए।
Exercise 3: Cargo.toml में overflow प्रोटेक्शन डिसेबल होने पर, a = 0 और b = 1 के साथ let result = a.checked_sub(b).unwrap(); करें। क्या होता है?
क्या आपको अपने Anchor प्रोजेक्ट के लिए Cargo.toml फ़ाइल में बस overflow-checks = true ही छोड़ देना चाहिए? आम तौर पर, हाँ। लेकिन यदि आप कुछ गहन कैलकुलेशंस (intensive calculations) कर रहे हैं, तो आप overflow-checks को false पर सेट करना चाह सकते हैं और compute cost बचाने के लिए महत्वपूर्ण जंक्चर्स पर रणनीतिक रूप से overflows से बचाव कर सकते हैं, जिसे हम आगे दिखाएँगे।
Solana compute units 101
Ethereum में, एक ट्रांज़ैक्शन तब तक चलता है जब तक कि यह ट्रांज़ैक्शन द्वारा निर्दिष्ट “gas limit” का उपभोग नहीं कर लेता। Solana “gas” को “compute unit” कहता है। डिफ़ॉल्ट रूप से, एक ट्रांज़ैक्शन 200,000 compute units तक सीमित होता है। यदि 200,000 से अधिक compute units की खपत होती है, तो ट्रांज़ैक्शन रिवर्ट (revert) हो जाता है।
Solana में एक ट्रांज़ैक्शन की compute costs का निर्धारण करना
Ethereum की तुलना में Solana वास्तव में उपयोग करने में सस्ता है, लेकिन इसका मतलब यह नहीं है कि Ethereum डेवेलपमेंट में आपके optimizoooor स्किल्स बेकार हैं। आइए मापें कि हमारे गणितीय (math) फ़ंक्शन्स को कितने compute units की आवश्यकता होती है।
Solana logs टर्मिनल यह भी दिखाता है कि कितने compute units का उपयोग किया गया था। हमने नीचे checked और unchecked घटाव (subtraction) के लिए बेंचमार्क प्रदान किए हैं।
overflow प्रोटेक्शन डिसेबल होने के साथ 824 compute units की खपत होती है:

overflow प्रोटेक्शन इनेबल होने के साथ 872 compute units की खपत होती है:

जैसा कि आप देख सकते हैं, केवल एक साधारण गणितीय (math) ऑपरेशन करने में लगभग 1000 यूनिट्स लगते हैं। चूँकि हमारे पास 200k यूनिट्स हैं, हम प्रति-ट्रांज़ैक्शन गैस लिमिट के भीतर केवल कुछ सौ साधारण arithmetic ऑपरेशन्स ही कर सकते हैं। इसलिए, जबकि Solana पर ट्रांज़ैक्शन्स आम तौर पर Ethereum की तुलना में सस्ते होते हैं, हम अभी भी अपेक्षाकृत छोटे compute unit कैप द्वारा सीमित हैं और Solana चेन पर fluid dynamic सिमुलेशन जैसे computationally intensive कार्यों को करने में सक्षम नहीं होंगे।
हम ट्रांज़ैक्शन कॉस्ट (transaction cost) पर बाद में फिर से विचार करेंगे।
Powers के लिए Solidity वाले समान सिंटैक्स (syntax) का उपयोग नहीं होता है
Solidity में, यदि हम x को y की पावर (power) तक बढ़ाना चाहते हैं, तो हम करते हैं:
uint256 result = x ** y;
Rust इस सिंटैक्स का उपयोग नहीं करता है। इसके बजाय, यह .pow का उपयोग करता है:
let x: u64 = 2; // it is important that the base's data type is explicit
let y = 3; // the exponent data type can be inferred
let result = x.pow(y);
यदि आप overflow के बारे में चिंतित हैं तो .checked_pow भी उपलब्ध है।
Floating points
स्मार्ट कॉन्ट्रैक्ट्स के लिए Rust का उपयोग करने की एक अच्छी बात यह है कि हमें मैथ (math) करने के लिए Solmate या Solady जैसी लाइब्रेरीज़ को आयात (import) नहीं करना पड़ता है। Rust एक काफी परिष्कृत (sophisticated) भाषा है जिसमें बहुत सारे ऑपरेशन्स इन-बिल्ट (built in) होते हैं, और अगर हमें कोड के किसी हिस्से की आवश्यकता होती है, तो हम इस काम को करने के लिए Solana इकोसिस्टम के बाहर एक Rust crate (Rust में लाइब्रेरीज़ को यही कहा जाता है) की तलाश कर सकते हैं।
आइए 50 का क्यूब रूट (cube root) निकालें। फ़्लोट्स के लिए क्यूब रूट फ़ंक्शन cbrt() फ़ंक्शन के साथ Rust भाषा में इन-बिल्ट है।
// note that we changed `a` to f32 (float 32)
// because `cbrt()` is not available for u64
pub fn initialize(ctx: Context<Initialize>, a: f32) -> Result<()> {
msg!("You said {:?}", a.cbrt());
Ok(());
}
याद है कि हमने पिछले अनुभाग में कैसे कहा था कि फ़्लोट्स computationally महंगे हो सकते हैं? खैर, यहाँ हम देखते हैं कि हमारे क्यूब रूट ऑपरेशन में unsigned integers पर साधारण arithmetic की तुलना में 5 गुना से अधिक खपत हुई:
Transaction executed in slot unspecified:
Signature: VfvySG5vvVSAnsYLCsvB9N6PsuGwL39kKd1fMsyvuB7y5DUHURwQVHU9rv3Xkz5NJqGHLSXoWoW92zJb5VKYCEF
Status: Ok
Log Messages:
Program 8o3ehd3XnyDocd9hG1uz5trbmSRB7gaLaE9BCXDpEnMY invoke [1]
Program log: Instruction: Initialize
Program log: attempting to begin the function with 50
Program log: Result = 3.6840315
Program 8o3ehd3XnyDocd9hG1uz5trbmSRB7gaLaE9BCXDpEnMY consumed 4860 of 200000 compute units
Program 8o3ehd3XnyDocd9hG1uz5trbmSRB7gaLaE9BCXDpEnMY success
Exercise 4: एक ऐसा कैलकुलेटर बनाएँ जो +, -, x, और ÷ करे। और साथ ही sqrt और log10 भी।
मूल रूप से 09 फरवरी, 2024 को प्रकाशित