Ethereum में, हम अक्सर एक require स्टेटमेंट देखते हैं जो एक फंक्शन आर्गुमेंट की वैल्यूज को प्रतिबंधित करता है। निम्नलिखित उदाहरण पर विचार करें:
function foobar(uint256 x) public {
require(x < 100, "I'm not happy with the number you picked");
// rest of the function logic
}
ऊपर दिए गए कोड में, यदि foobar को 100 या उससे अधिक की वैल्यू पास की जाती है, तो ट्रांजैक्शन revert हो जाएगा।
हम इसे Solana में, या विशेष रूप से, Anchor फ्रेमवर्क में कैसे करते हैं?
Anchor में Solidity के custom error और require स्टेटमेंट्स के समकक्ष (equivalents) मौजूद हैं। इस विषय पर उनका documentation काफी अच्छा है, लेकिन हम यह भी समझाएंगे कि जब फंक्शन आर्गुमेंट्स हमारे अनुसार न हों तो ट्रांजैक्शंस को कैसे रोका जाए।
नीचे दिए गए Solana प्रोग्राम में एक limit_range फंक्शन है जो केवल 10 से 100 तक (जिसमें ये दोनों भी शामिल हैं) की वैल्यूज को ही स्वीकार करेगा:
use anchor_lang::prelude::*;
declare_id!("8o3ehd3XnyDocd9hG1uz5trbmSRB7gaLaE9BCXDpEnMY");
#[program]
pub mod day4 {
use super::*;
pub fn limit_range(ctx: Context<LimitRange>, a: u64) -> Result<()> {
if a < 10 {
return err!(MyError::AisTooSmall);
}
if a > 100 {
return err!(MyError::AisTooBig);
}
msg!("Result = {}", a);
Ok(())
}
}
#[derive(Accounts)]
pub struct LimitRange {}
#[error_code]
pub enum MyError {
#[msg("a is too big")]
AisTooBig,
#[msg("a is too small")]
AisTooSmall,
}
निम्नलिखित कोड ऊपर दिए गए प्रोग्राम का यूनिट टेस्ट करता है:
import * as anchor from "@coral-xyz/anchor";
import { Program, AnchorError } from "@coral-xyz/anchor"
import { Day4 } from "../target/types/day4";
import { assert } from "chai";
describe("day4", () => {
// Configure the client to use the local cluster.
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.Day4 as Program<Day4>;
it("Input test", async () => {
// Add your test here.
try {
const tx = await program.methods.limitRange(new anchor.BN(9)).rpc();
console.log("Your transaction signature", tx);
} catch (_err) {
assert.isTrue(_err instanceof AnchorError);
const err: AnchorError = _err;
const errMsg =
"a is too small";
assert.strictEqual(err.error.errorMessage, errMsg);
console.log("Error number:", err.error.errorCode.number);
}
try {
const tx = await program.methods.limitRange(new anchor.BN(101)).rpc();
console.log("Your transaction signature", tx);
} catch (_err) {
assert.isTrue(_err instanceof AnchorError);
const err: AnchorError = _err;
const errMsg =
"a is too big";
assert.strictEqual(err.error.errorMessage, errMsg);
console.log("Error number:", err.error.errorCode.number);
}
});
});
अभ्यास:
- आप Error नंबर के साथ कौन सा पैटर्न देखते हैं? यदि आप
enum MyErrorमें errors का क्रम (order) बदलते हैं तो error codes का क्या होता है? - इस कोड ब्लॉक का उपयोग करें जो मौजूदा कोड में नया
funcऔर error जोड़ता है:
#[program]
pub mod day_4 {
use super::*;
pub fn limit_range(ctxThen : Context<LimitRange>, a: u64) -> Result<()> {
require!(a >= 10, MyError::AisTooSmall);
require!(a <= 100, MyError::AisTooBig);
msg!("Result = {}", a);
Ok(())
}
// NEW FUNCTION
pub fn func(ctx: Context<LimitRange>) -> Result<()> {
msg!("Will this print?");
return err!(MyError::AlwaysErrors);
}
}
#[derive(Accounts)]
pub struct LimitRange {}
#[error_code]
pub enum MyError {
#[msg("a is too small")]
AisTooSmall,
#[msg("a is too big")]
AisTooBig,
#[msg("Always errors")] // NEW ERROR, what do you think the error code will be?
AlwaysErrors,
}
और यह टेस्ट जोड़ें:
it("Error test", async () => {
// Add your test here.
try {
const tx = await program.methods.func().rpc();
console.log("Your transaction signature", tx);
} catch (_err) {
assert.isTrue(_err instanceof AnchorError);
const err: AnchorError = _err;
const errMsg =
"Always errors";
assert.strictEqual(err.error.errorMessage, errMsg);
console.log("Error number:", err.error.errorCode.number);
}
});
इसे रन करने से पहले, आपको क्या लगता है कि नया error code क्या होगा?
Ethereum और Solana द्वारा इनवैलिड पैरामीटर्स के साथ ट्रांजैक्शंस को रोकने के तरीके में महत्वपूर्ण अंतर यह है कि Ethereum एक revert ट्रिगर करता है और Solana एक error रिटर्न करता है।
require स्टेटमेंट्स का उपयोग करना
इसमें एक require! मैक्रो है, जो वैचारिक (conceptually) रूप से Solidity के require के समान ही है, जिसका उपयोग हम अपने कोड को समेकित (consolidate) करने के लिए कर सकते हैं। if चेक (जो तीन लाइनें लेते हैं) से require! कॉल्स पर स्विच करने से, हमारा पहले का कोड निम्नलिखित में बदल जाता है:
pub fn limit_range(ctx: Context<LimitRange>, a: u64) -> Result<()> {
require!(a >= 10, Day4Error::AisTooSmall);
require!(a <= 100, Day4Error::AisTooBig);
msg!("Result = {}", a);
Ok(())
}
Ethereum में, हम जानते हैं कि यदि कोई फंक्शन revert हो जाता है तो कुछ भी लॉग नहीं होता है, भले ही revert लॉग होने के बाद हुआ हो। उदाहरण के लिए, नीचे दिए गए कॉन्ट्रैक्ट में tryToLog पर किया गया कॉल कुछ भी लॉग नहीं करेगा, क्योंकि फंक्शन revert हो जाता है:
contract DoesNotLog {
event SomeEvent(uint256);
function tryToLog() public {
emit SomeEvent(100);
require(false);
}
}
अभ्यास: क्या होता है यदि आप किसी Solana प्रोग्राम फंक्शन में return error स्टेटमेंट्स से पहले एक msg! मैक्रो लगाते हैं? क्या होता है यदि आप return err! को Ok(()) से रिप्लेस करते हैं? नीचे हमारे पास एक फंक्शन है जो msg! के साथ कुछ लॉग करता है और फिर एक error रिटर्न करता है। देखें कि क्या msg! मैक्रो का कंटेंट लॉग होता है या नहीं।
pub fn func(ctx: Context<ReturnError>) -> Result<()> {
msg!("Will this print?");
return err!(Day4Error::AlwaysErrors);
}
#[derive(Accounts)]
pub struct ReturnError {}
#[error_code]
pub enum Day4Error {
#[msg("AlwaysErrors")]
AlwaysErrors,
}
आंतरिक रूप से (Under the hood), require! मैक्रो एक error रिटर्न करने से अलग नहीं है, यह सिर्फ syntactic sugar है।
अपेक्षित (expected) परिणाम यह है कि जब आप Ok(()) रिटर्न करेंगे तो “Will this print?” प्रिंट होगा और जब आप एक error रिटर्न करेंगे तो प्रिंट नहीं होगा।
Errors के संबंध में Solana और Solidity के बीच अंतर
Solidity में, require स्टेटमेंट revert op code के साथ एग्जीक्यूशन को रोक (halt) देता है। Solana एग्जीक्यूशन को नहीं रोकता बल्कि बस एक अलग वैल्यू रिटर्न करता है। यह उसी के अनुरूप (analogous) है जैसे linux सफलता (success) पर 0 या 1 रिटर्न करता है। यदि 0 रिटर्न होता है (Ok(()) रिटर्न करने के बराबर), तो इसका मतलब है सब कुछ सही तरीके से हो गया।
इसलिए, Solana प्रोग्राम्स को हमेशा कुछ न कुछ रिटर्न करना चाहिए — या तो एक Ok(()) या एक Error।
Anchor में, errors #[error_code] एट्रिब्यूट के साथ एक enum होते हैं।
ध्यान दें कि Solana में सभी फंक्शन्स का रिटर्न टाइप Result<()> कैसे होता है। एक result एक टाइप है जो या तो Ok(()) हो सकता है या एक error।
प्रश्न और उत्तर
Ok(()) के अंत में सेमीकोलन (semicolon) क्यों नहीं होता?
यदि आप इसे जोड़ते हैं, तो आपका कोड कंपाइल नहीं होगा। यदि Rust में अंतिम स्टेटमेंट में सेमीकोलन नहीं है, तो उस लाइन की वैल्यू रिटर्न हो जाती है।
Ok(()) में parenthesis का एक अतिरिक्त सेट क्यों होता है?
Rust में () का अर्थ “unit” होता है, जिसे आप C में void या Haskell में Nothing के रूप में सोच सकते हैं। यहाँ, Ok एक enum है जिसमें एक unit टाइप होता है। यही रिटर्न होता है। जो फंक्शन्स कुछ भी रिटर्न नहीं करते, वे Rust में निहित (implicitly) रूप से unit टाइप रिटर्न करते हैं। बिना सेमीकोलन वाला Ok(()) सिंटैक्स के आधार पर return Ok(()); के बराबर (equivalent) है। अंत में दिए गए सेमीकोलन पर ध्यान दें।
ऊपर दिए गए if statements में parenthesis क्यों गायब हैं?
वे Rust में वैकल्पिक (optional) होते हैं।
RareSkills के साथ और जानें
यह ट्यूटोरियल हमारे मुफ्त Solana course का हिस्सा है।
मूल रूप से 11 फरवरी, 2024 को प्रकाशित