Cairo उन सभी integer साइज़ की पूरी रेंज प्रदान नहीं करता है जो Solidity में पाई जाती हैं। जबकि Solidity 256 तक 8 बिट्स के हर गुणक (multiple) के लिए integer प्रकार (types) प्रदान करता है, Cairo केवल निम्नलिखित integer प्रकारों का समर्थन करता है:
u8u16u32u64u128u256
Rust से परिचित पाठकों के लिए, usize प्रकार (type) एक u32 है।
यहाँ एक Cairo कॉन्ट्रैक्ट (contract) का उदाहरण दिया गया है जो दो u256 नंबरों को एक साथ जोड़ता है:
#[starknet::interface]
pub trait IAdd<TContractState> {
fn add(self: @TContractState, a: u256, b: u256) -> u256;
}
#[starknet::contract]
mod Add {
#[storage]
struct Storage {}
#[abi(embed_v0)]
impl AddImpl of super::IAdd<ContractState> {
fn add(self: @ContractState, a: u256, b: u256) -> u256 {
a + b
}
}
}
Cairo निम्नलिखित प्रकार के signed integers का भी समर्थन करता है:
i8i16i32i64i128
ध्यान दें कि i256 समर्थित (supported) नहीं है।
इस लेख में, हम कवर करेंगे कि Cairo में integers कैसे काम करते हैं, और Solidity से इनके प्रमुख अंतरों पर प्रकाश डालेंगे। हम ओवरफ्लो बिहेवियर, integer साइज़ के बीच कास्टिंग (casting), और signed तथा unsigned वैल्यू के साथ काम करने जैसे कॉन्सेप्ट्स (concepts) पर चर्चा करेंगे। हम felt252 पर भी बात करेंगे, जो Cairo का नेटिव फील्ड एलिमेंट (native field element) है और जो सभी नंबर ऑपरेशन्स के मूल (core) में स्थित है।
Integers में overflow और underflow प्रोटेक्शन होता है
Cairo में integer (signed और unsigned) प्रकारों के लिए ओवरफ्लो प्रोटेक्शन डिफ़ॉल्ट रूप से सक्षम (enabled) होता है। इसे देखने के लिए, एक नया scarb प्रोजेक्ट scarb new integers बनाएँ, फिर ./src/lib.cairo में मौजूद डिफ़ॉल्ट कॉन्ट्रैक्ट को डिलीट करें और इसे निम्नलिखित कोड से बदलें:
#[starknet::interface]
pub trait IHelloStarknet<TContractState> {
fn underflow(ref self: TContractState, x: u256, y: u256) -> u256;
}
#[starknet::contract]
mod HelloStarknet {
#[storage]
struct Storage {}
#[abi(embed_v0)]
impl HelloStarknetImpl of super::IHelloStarknet<ContractState> {
fn underflow(ref self: ContractState, x: u256, y: u256) -> u256 {
x - y
}
}
}
स्वचालित (automatically) रूप से बनाए गए टेस्ट हटा दें और नीचे दिया गया कोड जोड़ें। ध्यान दें कि फ़ंक्शन के ऊपर मौजूद #[should_panic] मैक्रो यह निर्दिष्ट करता है कि यदि निष्पादन (execution) पैनिक (panic) होता है तो टेस्ट पास हो जाता है।
use snforge_std::{ContractClassTrait, DeclareResultTrait, declare};
use integers::{ IHelloStarknetDispatcher, IHelloStarknetDispatcherTrait};
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]
#[should_panic]
fn test_flow_protection() {
let contract_address = deploy_contract("HelloStarknet");
let dispatcher = IHelloStarknetDispatcher { contract_address };
dispatcher.underflow(0, 1);
}
scarb test के साथ टेस्ट चलाएँ और ध्यान दें कि टेस्ट पास हो जाता है।
कोई फ्लोटिंग पॉइंट्स (floating points) नहीं
Solidity की तरह, Cairo फ्लोटिंग पॉइंट्स (floating points) का समर्थन नहीं करता है।
Integers की कास्टिंग (Casting)
कास्ट (casts) दो प्रकार के होते हैं:
- ऐसे कास्ट्स जिनके सफल होने की गारंटी होती है और
- वे जो विफल (fail) हो सकते हैं।
उदाहरण के लिए, एक u8 को u16 में कास्ट करना हमेशा सफल होगा क्योंकि एक u16 किसी भी उस नंबर को होल्ड कर सकता है जिसे एक u8 कर सकता है। हालाँकि, u16 से u8 में कास्टिंग विफल हो सकती है क्योंकि कुछ सबसे महत्वपूर्ण बिट्स (most significant bits) ट्रंकेट (truncate) हो सकते हैं।
i16 से u16 में कास्टिंग विफल हो सकती है यदि i16 में कोई नेगेटिव वैल्यू हो। u16 से i16 में कास्टिंग विफल हो सकती है यदि u16 में रखा गया नंबर बहुत बड़ा है — signed integers के समान बिट साइज़ वाले unsigned integers, signed integers की तुलना में बड़े पॉजिटिव नंबर्स होल्ड कर सकते हैं।
कास्टिंग जो हमेशा सफल होती है
किसी बड़े प्रकार (type) में कनवर्ट करना हमेशा सफल होगा क्योंकि टारगेट प्रकार, सोर्स प्रकार से किसी भी वैल्यू को होल्ड कर सकता है।
हमेशा सफल होने वाले कास्ट का उदाहरण:
u8→u16,u32,u64,u128,u256u16→u32,u64,u128,u256i8→i16,i32,i64,i128i16→i32,i64,i128
एक ऐसा कास्ट करने के लिए जो हमेशा सफल हो, .into() का उपयोग करें
let small: u8 = 7;
let large: u16 = small.into(); // Always succeeds - u16 can hold any u8 value
कास्टिंग जो विफल हो सकती है
Solidity के विपरीत, जो एक बड़े नंबर को छोटे प्रकार में कास्ट किए जाने पर चुपचाप सबसे महत्वपूर्ण बिट्स (most significant bits) को ट्रंकेट कर देता है, यदि कास्ट सुरक्षित रूप से नहीं किया जा सकता है तो Cairo पैनिक (panic) करेगा।
यहाँ एक ऐसे कास्ट के लिए कोड स्निपेट (code snippet) है जो विफल हो सकता है:
// may fail if value is too large
let large: u16 = 300;
let small: u8 = large.try_into().unwrap(); // Panics! 300 > 255
उपरोक्त कोड पैनिक करता है क्योंकि वैल्यू 300 एक u8 में फिट नहीं होती है, जो केवल 255 तक की वैल्यू ही स्टोर कर सकता है। अभी के लिए, बस याद रखें कि unwrap() मेथड एक “रैपर (wrapper)” प्रकार (जिसकी चर्चा अगले सब-सेक्शन में की गई है) से वैल्यू निकालता है। यदि रैपर में एक वैध वैल्यू के बजाय कोई त्रुटि (error) होती है, तो unwrap() पैनिक करेगा।
ध्यान दें कि यदि आप ऐसी स्थिति में into() कास्ट का उपयोग करने का प्रयास करते हैं जहाँ कास्ट विफल हो सकता है (बड़े से छोटे प्रकार में कास्टिंग), तो कोड संकलित (compile) नहीं होगा।
यह पता लगाना कि क्या कोई कास्ट विफल होगा
try_into() का उपयोग करके integer प्रकारों के बीच कनवर्ट करते समय, परिणाम टारगेट प्रकार में फिट हो भी सकता है और नहीं भी। इसके कारण, कन्वर्शन एक Option (एक “रैपर (wrapper)” प्रकार का उदाहरण) लौटाता है, जो Cairo का यह कहने का तरीका है कि “यह सफल हो सकता है, या यह विफल हो सकता है।”
- यदि कन्वर्शन काम करता है, तो आपको
Option::Some(value)मिलता है। - यदि यह फिट नहीं होता है, तो आपको
Option::Noneमिलता है।
यह हमें वैल्यू का उपयोग करने से पहले संभावित विफलता (failure) को सुरक्षित रूप से संभालने के लिए मजबूर करता है। ऐसा करने के दो सामान्य तरीके हैं:
.is_some()मेथड का उपयोग करना जो एक bool लौटाता है जो यह दर्शाता है कि ऑप्शन में कोई वैल्यू है या नहीं।if letका उपयोग करना।
.is_some() मेथड का उपयोग करना:
// Value 300 cannot fit into u8 (max 255), so try_into returns None
let value: u16 = 300;
let result_option: Option<u8> = value.try_into();
if result_option.is_some() {
// cast succeeded
} else {
// cast failed
}
if let का उपयोग करना:
if let Some(result) = result_option {
// cast succeeded, use `result`
} else {
// cast failed
}
कांस्टेंट्स (Constants)
Cairo में कांस्टेंट्स (constants) वे वैल्यू होती हैं जो कंपाइल समय (compile time) पर ज्ञात होती हैं और रनटाइम (runtime) पर बदली नहीं जा सकती हैं। उन्हें const कीवर्ड का उपयोग करके mod ब्लॉक के अंदर डिक्लेयर किया जाता है और उनका प्रकार (type) स्पष्ट रूप से निर्दिष्ट होना चाहिए, कुछ इस तरह:
const <NAME>: <Type> = <value>;
यहाँ बताया गया है कि Cairo में कांस्टेंट्स को कैसे डिक्लेयर और उपयोग करें:
#[starknet::contract]
mod HelloStarknet {
// DECLARE CONSTANTS
const num_one: u256 = 1;
const num_two: i8 = -2;
#[storage]
struct Storage {}
#[abi(embed_v0)]
impl HelloStarknetImpl of super::IHelloStarknet<ContractState> {
fn get_one(self: @ContractState) -> u256 {
// USE CONSTANTS
num_one
}
fn get_two(self: @ContractState) -> i8 {
// USE CONSTANTS
num_two
}
}
}
Constants बनाम Immutables
Solidity के विपरीत, Cairo में एक अलग immutable कीवर्ड नहीं होता है। वे वैल्यू जिन्हें कॉन्ट्रैक्ट डिप्लॉयमेंट के दौरान एक बार सेट करने की आवश्यकता होती है लेकिन कंपाइल समय पर ज्ञात नहीं होती हैं, उन्हें कॉन्ट्रैक्ट स्टोरेज में स्टोर किया जाना चाहिए और कंस्ट्रक्टर (constructor) में सेट किया जाना चाहिए।
अधिकतम (Max) integer साइज़
Solidity में, एक integer का अधिकतम साइज़ प्राप्त करने के लिए type(uint256).max का उपयोग किया जाता है। Cairo में, हम let max_u256: u256 = Bounded::MAX का उपयोग करते हैं जैसा कि नीचे दिखाया गया है:
#[starknet::contract]
mod HelloStarknet {
use core::num::traits::{Bounded}; // Bounded is how we get the max
#[storage]
struct Storage {} // unusued
#[abi(embed_v0)]
impl HelloStarknetImpl of super::IHelloStarknet<ContractState> {
fn max_demo(ref self: ContractState) -> u256 {
let max_u256: u256 = Bounded::MAX;
max_u256
}
}
}
उपरोक्त कोड में, कॉन्ट्रैक्ट पहले Bounded ट्रेट (trait) को इम्पोर्ट करता है, जो संख्यात्मक (numeric) कांस्टेंट्स तक पहुँच प्रदान करता है: MIN और MAX। MAX आपको उस प्रकार के लिए अनुमत (allowed) अधिकतम वैल्यू लौटाता है। max_demo() फ़ंक्शन के अंदर, हम u256 प्रकार का अधिकतम वैल्यू प्राप्त करने के लिए Bounded::MAX का उपयोग करते हैं।
अधिकांश समय, Cairo अपने आप प्रकारों (types) को निर्धारित करने में सक्षम होता है, लेकिन Bounded::MAX का उपयोग करते समय कंपाइलर को स्वचालित रूप से पता नहीं चलेगा कि आपको किस integer प्रकार के लिए अधिकतम (max) की आवश्यकता है। इसलिए, वेरिएबल को एक स्पष्ट प्रकार एनोटेशन (explicit type annotation) की आवश्यकता होती है, जो कि : के बाद u256 है, अर्थात् let max_u256: u256 = Bounded::MAX;।
न्यूनतम (Min) integer साइज़
जिस तरह हम integer प्रकारों के लिए अधिकतम वैल्यू प्राप्त कर सकते हैं, Cairo उसी तरह Bounded ट्रेट के माध्यम से Bounded::MIN के साथ उनके न्यूनतम (minimum) वैल्यू तक भी पहुँच प्रदान करता है, जैसा कि नीचे दिखाया गया है:
#[starknet::contract]
mod HelloStarknet {
use core::num::traits::{Bounded}; // Bounded provides both MIN and MAX
#[storage]
struct Storage {} // unused
#[abi(embed_v0)]
impl HelloStarknetImpl of super::IHelloStarknet<ContractState> {
fn min_demo(ref self: ContractState) -> (u256, i128) {
// This will be 0 for unsigned types
let min_u256: u256 = Bounded::MIN;
// This will be the most negative value
let min_i128: i128 = Bounded::MIN;
(min_u256, min_i128)
}
}
}
Min साइज़ को समझना
Unsigned integer प्रकारों (u8, u16, u32, u64, u128, u256) के लिए, न्यूनतम वैल्यू हमेशा 0 होता है:
let min_u8: u8 = Bounded::MIN; // 0
let min_u16: u16 = Bounded::MIN; // 0
let min_u32: u32 = Bounded::MIN; // 0
let min_u64: u64 = Bounded::MIN; // 0
let min_u128: u128 = Bounded::MIN; // 0
let min_u256: u256 = Bounded::MIN; // 0
Signed integer प्रकारों (i8, i16, i32, i64, i128) के लिए, न्यूनतम वैल्यू सबसे अधिक नेगेटिव नंबर होता है जिसे दर्शाया जा सकता है:
let min_i8: i8 = Bounded::MIN; // -128
let min_i16: i16 = Bounded::MIN; // -32,768
let min_i32: i32 = Bounded::MIN; // -2,147,483,648
let min_i64: i64 = Bounded::MIN; // -9,223,372,036,854,775,808
let min_i128: i128 = Bounded::MIN; // a very large negative value
Type एनोटेशन की आवश्यकता
ठीक Bounded::MAX की तरह, Bounded::MIN का उपयोग करते समय कंपाइलर स्वचालित रूप से प्रकार (type) का अनुमान नहीं लगा सकता है, इसलिए स्पष्ट टाइप एनोटेशन की आवश्यकता होती है:
// This won't compile - ambiguous type ❌
let min_val = Bounded::MIN;
// This will compile - explicit type annotation ✅
let min_val: u64 = Bounded::MIN;
Integer लिटरल्स (literals) पर प्रकार निर्दिष्ट करने के लिए शॉर्टहैंड (Shorthand)
यदि हम किसी integer को एक निश्चित वैल्यू असाइन करते हैं, तो हम स्पष्ट रूप से integer का प्रकार निर्दिष्ट कर सकते हैं या कंपाइलर को प्रकार का अनुमान (infer) लगाने दे सकते हैं।
प्रकार (type) निर्दिष्ट करना:
// first way
let x: i32 = 10;
// second way
let y = 10_i32;
कंपाइलर को प्रकार का अनुमान (infer) लगाने की अनुमति देना:
यदि हम प्रकार निर्दिष्ट नहीं करते हैं, तो कंपाइलर संदर्भ (context) से इसका अनुमान लगाने का प्रयास करेगा। उदाहरण के लिए, निम्नलिखित फ़ंक्शन एक u32 लौटाता है इसलिए 10 का प्रकार u32 है:
fn hello_world() -> u32 {
let x = 10;
x
}
Signed integer डिवीज़न (division) ओवरफ्लो
Solidity में, signed integer डिवीज़न (division) के साथ एक विशिष्ट एज केस (edge case) होता है जो अनपेक्षित बिहेवियर का कारण बन सकता है। इस Solidity कॉन्ट्रैक्ट पर विचार करें:
contract D {
function div(int8 a, int8 b) public pure returns (int8 c) {
c = a / b;
}
}
समस्या तब होती है जब आप सबसे अधिक नेगेटिव वैल्यू को -1 से विभाजित करते हैं। int8 के लिए, रेंज -128 से 127 है। जब आप -128 / -1 करते हैं, तो गणितीय रूप से परिणाम 128 होना चाहिए, लेकिन 128 एक int8 में फिट नहीं हो सकता (जिसकी अधिकतम वैल्यू 127 होती है)। इसके कारण ओवरफ्लो होता है।
Solidity में, यह ऑपरेशन या तो:
- एक अनपेक्षित वैल्यू में रैप अराउंड (Wrap around) हो जाएगा
- रिवर्ट (Revert) हो जाएगा (नए वर्ज़न्स में ओवरफ्लो प्रोटेक्शन के साथ)
Cairo integer डिवीज़न ओवरफ्लो को कैसे संभालता है
बिल्कुल Solidity वर्ज़न ≥ 0.8 की तरह, Cairo बिल्ट-इन ओवरफ्लो प्रोटेक्शन प्रदान करता है। यदि किसी ऑपरेशन का परिणाम ओवरफ्लो होगा, तो प्रोग्राम रनटाइम पर पैनिक (panic) करेगा, जिससे अनपेक्षित बिहेवियर को रोका जा सकेगा।
#[starknet::contract]
mod Div {
#[storage]
struct Storage {}
#[abi(embed_v0)]
impl DivImpl of super::IDiv<ContractState> {
fn div(self: @ContractState, a: i8, b: i8) -> i8 {
// This will panic if `a` is -128 and `b` is -1
a / b
}
}
}
Signed डिवीज़न ओवरफ्लो से होने वाले पैनिक को रोकने के लिए, हमें ऑपरेशन करने से पहले मैन्युअल रूप से शर्तों (conditions) की जाँच करने की आवश्यकता होती है, कुछ इस तरह:
#[starknet::contract]
mod Div {
use core::num::traits::Bounded;
#[storage]
struct Storage {}
#[abi(embed_v0)]
impl DivImpl of super::IDiv<ContractState> {
fn div(self: @ContractState, a: i8, b: i8) -> i8 {
if b == 0 {
// Division by zero
} else if a == Bounded::<i8>::MIN && b == -1 {
// Overflow case
} else {
a / b
}
}
}
}
Casting Up विफलता (Failure)
यह Solidity फ़ंक्शन सुरक्षित दिखता है लेकिन अनपेक्षित परिणाम दे सकता है:
function mul(uint8 a, uint8 b) public pure returns (uint256 c) {
c = a * b;
}
समस्या यह है कि गुणा a * b पहले uint8 अंकगणित (arithmetic) में होता है, फिर परिणाम को uint256 में कास्ट किया जाता है। यदि a * b की वैल्यू uint8 रेंज (0-255) से ओवरफ्लो होती है, तो गुणा ऊपर कास्ट होने से पहले ही रैप अराउंड (wrap around) हो जाता है।
उदाहरण के लिए:
mul(200, 200)गणितीय रूप से40000लौटाना चाहिए- लेकिन
200 * 200 = 40000,uint8(अधिकतम 255) को ओवरफ्लो कर देता है uint8में रैप (wrapped) किया गया परिणाम40000 % 256 = 64होगा- फिर
64कोuint256में कास्ट किया जाता है, जो40000के बजाय64लौटाता है, बेशक यह 0.8 से कम के Solidity वर्ज़न्स में होता है
Cairo में ओवरफ्लो
Cairo अपने बिल्ट-इन ओवरफ्लो प्रोटेक्शन के माध्यम से कास्टिंग अप ओवरफ्लो समस्या को संभालता है। अर्थात्, यदि कोई ऑपरेशन ऐसा वैल्यू उत्पन्न करता है जो अनुमत (allowed) सीमा से परे जाता है, तो Cairo अनपेक्षित बिहेवियर की अनुमति देने के बजाय एक त्रुटि (error) देगा और निष्पादन को रोक देगा।
उदाहरण के लिए, नीचे दिया गया कोड पैनिक करेगा यदि a * b > 255:
// This will panic if the multiplication overflows u8
fn mul(self: @ContractState, a: u8, b: u8) -> u256 {
let result_u8 = a * b; // Panic if a * b > 255
result_u8.into() // This line never executes if overflow occurs
}
सुरक्षित Casting Up
हमारे Cairo कॉन्ट्रैक्ट को ओवरफ्लो के कारण पैनिक करने से बचाने के लिए एक सुरक्षित तरीका यह है कि जब हमें बड़े प्रकार (type) में परिणाम की आवश्यकता हो तो अंकगणितीय (arithmetic) ऑपरेशन्स से पहले कास्ट अप (cast up) करें। उदाहरण के लिए, हम u8 से u256 में कास्ट कर सकते हैं:
// cast up before multiplication
fn safe_mul(self: @ContractState, a: u8, b: u8) -> u256 {
let a_wide: u256 = a.into();
let b_wide: u256 = b.into();
a_wide * b_wide // No overflow possible
}
Exponents (घातांक)
Solidity में, घातांक (exponents) के लिए सिंटैक्स b ** e है जहाँ b बेस है और e एक्सपोनेंट (घातांक) है।
Cairo में, आपको use core::num::traits::Pow; के साथ Pow को इम्पोर्ट करना होगा। फिर, आप b.pow(e) का उपयोग करके किसी integer की घात (power) बढ़ा सकते हैं।
#[starknet::contract]
mod HelloStarknet {
use core::num::traits::Pow; // THIS IMPORT IS REQUIRED
#[storage]
struct Storage {}
#[abi(embed_v0)]
impl HelloStarknetImpl of super::IHelloStarknet<ContractState> {
fn upcast_demo(ref self: ContractState, x: u256, y: u32) -> u256 {
x.pow(y) // compute exponent
}
}
}
.pow() मेथड बेस के समान प्रकार का वैल्यू लौटाता है, इसलिए यहाँ x.pow(y) एक प्रकार (u256) की वैल्यू उत्पन्न करता है।
महत्वपूर्ण: एक्सपोनेंट u32 (या usize जो Cairo में आंतरिक रूप से एक u32 ही है) प्रकार का होना चाहिए। यदि किसी अन्य integer प्रकार का उपयोग किया जाता है तो कोड संकलित (compile) नहीं होगा।
लिटरल्स (literals) में अंडरस्कोर (Underscores)
Solidity की तरह, Cairo में बड़े नंबरों को आसानी से पढ़ने के लिए अंडरस्कोर के साथ तोड़ा जा सकता है:
// valid Cairo
let basis_points = 10_000;
साइंटिफिक नोटेशन (Scientific Notation) शॉर्टहैंड
Solidity में, आप साइंटिफिक नोटेशन का उपयोग करके नंबर लिख सकते हैं जैसे 10e18, जो को दर्शाता है। Cairo इसका समर्थन नहीं करता है। Cairo में 10e18 लिखने के लिए, Pow ट्रेट का उपयोग करें जैसा कि नीचे दिखाया गया है:
use core::num::traits::Pow;
// ...
let num = 10_u256.pow(18_u32);
याद रखें, एक्सपोनेंट u32 प्रकार का होना चाहिए।
Bitwise ऑपरेशन्स, Shifting ऑपरेशन्स, और Comparisons (तुलनाएँ)
Bitwise ऑपरेशन्स
Cairo integer प्रकारों पर मानक bitwise ऑपरेशन्स का समर्थन करता है:
Bitwise AND (&):
let a: u8 = 0b1100; // 12 in decimal
let b: u8 = 0b1010; // 10 in decimal
let result = a & b; // 0b1100 & 0b1010 = 0b1000 => 8
Bitwise OR (|):
let a: u8 = 0b1100; // 12 in decimal
let b: u8 = 0b1010; // 10 in decimal
let result = a | b; // 0b1110 = 14
Bitwise XOR (^):
let a: u8 = 0b1100; // 12 in decimal
let b: u8 = 0b1010; // 10 in decimal
let result = a ^ b; // 0b0110 = 6
Bitwise NOT (~):
let a: u8 = 0b1100; // 12 in decimal
let result = ~a; // 0b11110011 = 243 (inverts all bits)
Shifting ऑपरेशन्स
Cairo लेफ्ट और राइट बिट शिफ्टिंग (bit shifting) ऑपरेशन्स प्रदान करता है:
लेफ्ट शिफ्ट (Left Shift) (<<)
बिट्स को बायीं ओर शिफ्ट करता है, और शून्य (zeros) से भरता है:
let a: u8 = 0b0001; // 1 in decimal
let result = a << 3; // 0b1000 = 8 (multiplies by 2**3)
राइट शिफ्ट (Right Shift) (>>)
बिट्स को दायीं ओर शिफ्ट करता है:
let a: u8 = 0b1100; // 12 in decimal
let result = a >> 2; // 0b0011 = 3 (divides by 2**2)
Comparison ऑपरेशन्स
Cairo सभी मानक कम्पेरिज़न (comparison) ऑपरेटरों का समर्थन करता है:
समानता (Equality) (== और !=)
let a: u32 = 10;
let b: u32 = 20;
let equal = a == b; // false
let not_equal = a != b; // true
क्रम (Ordering) (<, <=, >, >=)
let a: u32 = 10;
let b: u32 = 20;
let less_than = a < b; // true
let less_or_equal = a <= b; // true
let greater_than = a > b; // false
let greater_or_equal = a >= b; // false
felt252 के बारे में एक नोट
यदि आप पुराने प्रोडक्शन Cairo कोड को पढ़ते हैं, तो आप felt252 डेटाटाइप का अक्सर उपयोग होता देखेंगे। जिस तरह EVM में 256 बिट्स का डिफ़ॉल्ट वर्ड साइज़ (word size) होता है, CairoVM का डिफ़ॉल्ट वर्ड साइज़ 252 बिट्स से थोड़ा कम होता है, या स्पष्ट रूप से कहें तो: 3618502788666131213697322783095070105623107215331596699973092056135872020481 या 2²⁵¹+17⋅2¹⁹²+1। यह संख्या 2²⁵² से थोड़ी छोटी है।
Cairo उन संख्या प्रकारों को felt252 के रूप में संदर्भित करता है जो [0…2²⁵¹+17⋅2¹⁹²+1] की सीमा (range) में आते हैं।
यह बड़ी संख्या एक अभाज्य संख्या (prime number) है जो Cairo वर्चुअल मशीन पर जीरो-नॉलेज प्रूफ (zero-knowledge proof) गणित के लिए ऑप्टिमाइज़ की गई है।
felt252 नाम “field element that fits in 252 bits (फील्ड एलिमेंट जो 252 बिट्स में फिट बैठता है)” शब्द से आया है। एक “फील्ड एलिमेंट” एक ऐसा नंबर है जो एक ऐसी नंबर प्रणाली (number system) में रहता है जहाँ सभी जोड़ और गुणा किसी अभाज्य संख्या (prime number) के मॉड्यूलो (modulo) किए जाते हैं।
Cairo कोड में felt252 का उपयोग करने की अनुशंसा (recommendation) नहीं की जाती है क्योंकि भविष्य में, CairoVM लेनदेन (transactions) को साबित करने की अपनी गति (speed) में सुधार करने के लिए अपने डिफ़ॉल्ट वर्ड साइज़ को एक छोटे वैल्यू में बदल सकता है।
Cairo कंपाइलर आपके लिए परदे के पीछे integers (u8… u256) के felt252 में अनुवाद (translation) को सहजता से संभालेगा। यह ध्यान देने योग्य है कि एक u256 252 बिट्स में फिट नहीं होता है, इसलिए परदे के पीछे, एक u256 वास्तव में दो felt252 एलिमेंट्स होते हैं। इस प्रकार, गैस-दक्षता (gas-efficiency) कारणों से जहाँ संभव हो u128 या छोटे integers का उपयोग करना बेहतर है। केवल एक अन्य स्थिति जहाँ felt252 का उपयोग करना समझदारी है, वह तब है जब अत्यधिक ऑप्टिमाइज़ेशन आवश्यक हो। हम बाद के ट्यूटोरियल में Starknet पर गैस लागतों (gas costs) पर फिर से विचार करेंगे। अभी के लिए, हम अनुशंसा करते हैं कि आप felt252 प्रकार का उपयोग न करें और बस integers का उपयोग करें।
हालाँकि, क्योंकि आप कोड में बार-बार felt252 देखेंगे, यह समझाना उचित है कि यह कैसे काम करता है।
felt252 में कोई ओवरफ्लो और अंडरफ्लो प्रोटेक्शन नहीं है
Solidity 0.8.0 या उच्चतर वर्ज़न के विपरीत, Cairo में felt252 के लिए ओवरफ्लो और अंडरफ्लो प्रोटेक्शन इन-बिल्ट (built-in) नहीं है। इसे प्रदर्शित करने के लिए, scarb new numbers नामक एक नया प्रोजेक्ट बनाएँ। फिर lib.cairo में जनरेट हुए कोड को निम्नलिखित कोड से बदलें:
#[starknet::interface]
pub trait IHelloStarknet<TContractState> {
fn math_demo(self: @TContractState, x: felt252, y: felt252) -> felt252;
}
#[starknet::contract]
mod HelloStarknet {
#[storage]
struct Storage {}
#[abi(embed_v0)]
impl HelloStarknetImpl of super::IHelloStarknet<ContractState> {
fn math_demo(self: @ContractState, x: felt252, y: felt252) -> felt252 {
x - y
}
}
}
टेस्ट को इस प्रकार बदलें:
use starknet::ContractAddress;
use snforge_std::{declare, ContractClassTrait, DeclareResultTrait};
use numbers::IHelloStarknetDispatcher;
use numbers::IHelloStarknetDispatcherTrait;
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_math_demo() {
let contract_address = deploy_contract("HelloStarknet");
let dispatcher = IHelloStarknetDispatcher { contract_address };
let result = dispatcher.math_demo(0, 1); // 0 - 1
println!("result: {}", result);
}
कंसोल प्रिंट करेगा:
result: 3618502788666131213697322783095070105623107215331596699973092056135872020480
0.8.0 से कम के Solidity वर्ज़न्स में, unsigned अंकगणित (arithmetic) को मानते हुए, 0 - 1 के परिणामस्वरूप अंडरफ्लो होता है। फिर वैल्यू अधिकतम संभव uint256 वैल्यू में रैप (wrap) हो जाती है। Cairo में felt252 के साथ भी कुछ ऐसा ही होता है, क्योंकि इसमें कोई ओवरफ्लो या अंडरफ्लो प्रोटेक्शन नहीं है। सभी अंकगणितीय कार्य फील्ड प्राइम (2²⁵¹ + 17 × 2¹⁹² + 1) के मॉड्यूलो (modulo) किए जाते हैं, इसलिए 0 में से 1 घटाने पर सबसे बड़ी वैध (valid) felt252 वैल्यू वापस आती है, जो एक बड़ी संख्या की तरह दिखती है।
felt252_div
यदि आप एक felt252 को दूसरे felt252 से विभाजित करने का प्रयास करते हैं, तो आपको एक कंपाइलेशन एरर (compilation error) मिलेगा। निम्नलिखित कोड संकलित (compile) नहीं होगा:
fn math_demo(self: @ContractState, x: felt252, y: felt252) -> felt252 {
x / y
}
यह पूरी तरह से समझने के लिए कि Cairo इस तरह के डिवीज़न (विभाजन) की अनुमति क्यों नहीं देता है, modular arithmetic पर हमारा वीडियो देखें।
felt252 को विभाजित (Divide) करने का सही तरीका
felt252 वैल्यूज़ के साथ डिवीज़न करने के लिए, हमें felt252_div का उपयोग करना होगा जो Cairo की कोर लाइब्रेरी में एक बिल्ट-इन (built-in) फ़ंक्शन है:
#[starknet::contract]
mod HelloStarknet {
// THIS IS NEW
use core::felt252_div;
#[storage]
struct Storage {}
#[abi(embed_v0)]
impl HelloStarknetImpl of super::IHelloStarknet<ContractState> {
fn math_demo(self: @ContractState) -> felt252 {
felt252_div(4, 2)
}
}
}
felt252_div फ़ंक्शन नियमित डिवीज़न नहीं करता है। इसके बजाय, यह:
- डिवाईज़र (divisor)
yका फील्ड प्राइम (field prime) मॉड्यूलो का मॉड्यूलर इन्वर्स (modular inverse) खोजता है xको इस इन्वर्स से गुणा करता है- फील्ड प्राइम के मॉड्यूलो के रूप में परिणाम लौटाता है
गणितीय रूप से:
felt252_div(x, y) = x * y^(-1) mod P
जहाँ फाइनाइट फील्ड (finite field) में y^(-1), y का मॉड्यूलर इन्वर्स (modular inverse) है।
शून्य से विभाजन (Division by Zero)
felt252_div(x, 0) का प्रयास करने पर रनटाइम पैनिक (runtime panic) होगा:
*// This will panic!*
let result = felt252_div(42, 0);
डिवीज़न करने से पहले हमेशा अपने डिवाईज़र (divisor) को मान्य (validate) करें। एक तरीका जिससे felt252_div यह सुनिश्चित करता है कि felt252 वैल्यू शून्य नहीं हो सकती, वह है NonZero<felt252> का उपयोग करना।
NonZero
felt252_div फ़ंक्शन को अपने दूसरे आर्गुमेंट (डिवाईज़र) के रूप में सादे (plain) felt252 के बजाय NonZero<felt252> प्रकार की आवश्यकता होती है। यह कंपाइल समय पर शून्य (zero) द्वारा विभाजन को रोकता है।
// BE SURE TO CHANGE THE TRAIT DEFINITION ALSO
#[starknet::contract]
mod HelloStarknet {
use core::felt252_div;
#[storage]
struct Storage {}
#[abi(embed_v0)]
impl HelloStarknetImpl of super::IHelloStarknet<ContractState> {
// NOTE THE TYPE OF `y`
fn underflow_demo(self: @ContractState, x: felt252, y: NonZero<felt252>) -> felt252 {
felt252_div(x, y)
}
}
}
सारांश (Summary)
Cairo integers सुरक्षित हैं। सभी u* और i* प्रकारों में ओवरफ्लो प्रोटेक्शन होता है और वे अमान्य ऑपरेशन्स (invalid operations) पर पैनिक (panic) करते हैं।
कास्टिंग सख्त (strict) है: .into() सुरक्षित है (बड़े प्रकार में कनवर्ट करते समय हमेशा सफल होता है), .try_into() त्रुटियों (errors) की जाँच करता है (पैनिक कर सकता है यदि टारगेट प्रकार वैल्यू को होल्ड नहीं कर सकता)।
घातांक (Exponentiation) ट्रेट इम्पोर्ट के माध्यम से .pow() का उपयोग करता है।
Bitwise और comparison ऑपरेटर integer प्रकारों में सामान्य रूप से काम करते हैं।
felt252 Cairo का नेटिव फील्ड एलिमेंट (native field element) है जिसमें integers की तरह ओवरफ्लो चेक नहीं होता है। felt पर डिवीज़न के लिए ज़ीरो-चेकिंग (zero-checking) वाले फ़ंक्शन (felt252_div) की आवश्यकता होती है।
यह लेख Cairo Programming on Starknet पर ट्यूटोरियल सीरीज़ का एक हिस्सा है।