पिछले ट्यूटोरियल्स में, seeds=[] पैरामीटर हमेशा खाली था। यदि हम इसमें डेटा डालते हैं, तो यह Solidity मैपिंग में एक की (key) या कई कीज़ (keys) की तरह व्यवहार करता है।
निम्नलिखित उदाहरण पर विचार करें:
contract ExampleMapping {
struct SomeNum {
uint64 num;
}
mapping(uint64 => SomeNum) public exampleMap;
function setExampleMap(uint64 key, uint64 val) public {
exampleMap[key] = SomeNum(val);
}
}
अब हम एक Solana Anchor प्रोग्राम example_map बनाते हैं।
मैपिंग को इनिशियलाइज़ करना: Rust
शुरुआत में, हम केवल इनिशियलाइज़ेशन (initialization) स्टेप दिखाएंगे क्योंकि इसमें कुछ नया सिंटैक्स (syntax) पेश होगा जिसे हमें समझाना होगा।
use anchor_lang::prelude::*;
use std::mem::size_of;
declare_id!("DntexDPByFxpVeBSjd6nLqQQSqZmSaDkP8TUbcJ9jAgt");
#[program]
pub mod example_map {
use super::*;
pub fn initialize(ctx: Context<Initialize>, key: u64) -> Result<()> {
Ok(())
}
}
#[derive(Accounts)]
#[instruction(key: u64)]
pub struct Initialize<'info> {
#[account(init,
payer = signer,
space = size_of::<Val>() + 8,
seeds=[&key.to_le_bytes().as_ref()],
bump)]
val: Account<'info, Val>,
#[account(mut)]
signer: Signer<'info>,
system_program: Program<'info, System>,
}
#[account]
pub struct Val {
value: u64,
}
यहाँ बताया गया है कि आप मैप के बारे में कैसे सोच सकते हैं:
&key.to_le_bytes().as_ref() में seeds पैरामीटर key को मैप की एक “key” के रूप में माना जा सकता है, जो इस Solidity कंस्ट्रक्शन के समान है:
mapping(uint256 => uint256) myMap;
myMap[key] = val
कोड के अपरिचित (unfamiliar) हिस्से #[instruction(key: u64)] और seeds=[&key.to_le_bytes().as_ref()] हैं।
seeds = [&key.to_le_bytes().as_ref()]
seeds में आइटम्स के बाइट्स (bytes) होने की उम्मीद की जाती है। हालाँकि, हम एक u64 पास कर रहे हैं जो बाइट्स टाइप का नहीं है। इसे बाइट्स में बदलने के लिए, हम to_le_bytes() का उपयोग करते हैं। “le” का अर्थ “little endian” है। Seeds को लिटल एंडियन बाइट्स के रूप में एन्कोड (encode) किया जाना आवश्यक नहीं है, हमने इस उदाहरण के लिए बस इसे चुना है। बिग एंडियन (Big endian) भी काम करता है जब तक कि आप कंसिस्टेंट (consistent) हों। बिग एंडियन में बदलने के लिए, हमने to_be_bytes() का उपयोग किया होता।
#[instruction(key: u64)]
फ़ंक्शन आर्गुमेंट key को initialize(ctx: Context<Initialize>, key: u64) में “पास” करने के लिए हमें instruction मैक्रो का उपयोग करने की आवश्यकता है, अन्यथा हमारे init मैक्रो के पास initialize से key आर्गुमेंट को “देखने” का कोई तरीका नहीं है।
मैपिंग को इनिशियलाइज़ करना: Typescript
नीचे दिया गया कोड दिखाता है कि अकाउंट को कैसे इनिशियलाइज़ किया जाए:
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { ExampleMap } from "../target/types/example_map";
describe("example_map", () => {
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.ExampleMap as Program<ExampleMap>;
it("Initialize mapping storage", async () => {
const key = new anchor.BN(42);
const seeds = [key.toArrayLike(Buffer, "le", 8)];
let valueAccount = anchor.web3.PublicKey.findProgramAddressSync(
seeds,
program.programId
)[0];
await program.methods.initialize(key).accounts({val: valueAccount}).rpc();
});
});
कोड key.toArrayLike(Buffer, "le", 8) यह निर्दिष्ट (specify) करता है कि हम key के मान का उपयोग करके 8 बाइट्स आकार का एक बाइट्स बफ़र बनाने का प्रयास कर रहे हैं। हमने 8 बाइट्स को इसलिए चुना क्योंकि हमारी की (key) 64 बिट्स की है, और 64 बिट्स 8 बाइट्स के बराबर होते हैं। “le” लिटल एंडियन है ताकि हम Rust कोड से मेल खा सकें।
मैपिंग में प्रत्येक “वैल्यू” (value) एक अलग अकाउंट है और इसे अलग से इनिशियलाइज़ किया जाना चाहिए।
मैपिंग को सेट करना: Rust
वैल्यू सेट करने के लिए हमें जिस अतिरिक्त Rust कोड की आवश्यकता है वह यहाँ दिया गया है। यहाँ का सभी सिंटैक्स परिचित (familiar) होना चाहिए।
// inside the #[program] module
pub fn set(ctx: Context<Set>, key: u64, val: u64) -> Result<()> {
ctx.accounts.val.value = val;
Ok(())
}
//...
#[derive(Accounts)]
#[instruction(key: u64)]
pub struct Set<'info> {
#[account(mut)]
val: Account<'info, Val>,
}
मैपिंग को सेट और रीड करना: Typescript
चूँकि हम उस अकाउंट एड्रेस को डिराइव (derive) करते हैं जहाँ क्लाइंट (Typescript) में वैल्यू स्टोर होती है, हम इससे वैसे ही रीड और राइट करते हैं जैसे हम उन अकाउंट्स के साथ करते हैं जिनका seeds एरे (array) खाली होता है। Solana अकाउंट डेटा को रीड करने और राइट करने का सिंटैक्स पिछले ट्यूटोरियल्स के समान ही है:
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { ExampleMap } from "../target/types/example_map";
describe("example_map", () => {
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.ExampleMap as Program<ExampleMap>;
it("Initialize and set value", async () => {
const key = new anchor.BN(42);
const value = new anchor.BN(1337);
const seeds = [key.toArrayLike(Buffer, "le", 8)];
let valueAccount = anchor.web3.PublicKey.findProgramAddressSync(
seeds,
program.programId
)[0];
await program.methods.initialize(key).accounts({val: valueAccount}).rpc();
// set the account
await program.methods.set(key, value).accounts({val: valueAccount}).rpc();
// read the account back
let result = await program.account.val.fetch(valueAccount);
console.log(`the value ${result.value} was stored in ${valueAccount.toBase58()}`);
});
});
“nested mappings” को स्पष्ट करना
Python या JavaScript जैसी भाषाओं में, एक सही नेस्टेड मैपिंग (true nested mapping) एक हैशमैप (hashmap) होता है जो किसी अन्य हैशमैप को पॉइंट करता है।
हालाँकि Solidity में, “nested mappings” केवल एक सिंगल मैप होते हैं जहाँ मल्टीपल कीज़ (multiple keys) इस तरह व्यवहार करती हैं मानो वे एक ही की (key) हों।
एक “सही” नेस्टेड मैपिंग में, आप केवल पहली की (key) प्रदान कर सकते हैं और बदले में आपको एक अन्य हैशमैप रिटर्न के रूप में मिल सकता है।
Solidity के “nested mappings” “सही” नेस्टेड मैपिंग नहीं हैं: आप एक की (key) देकर मैप वापस प्राप्त नहीं कर सकते हैं: आपको सभी कीज़ (keys) प्रदान करनी होंगी और अंतिम परिणाम प्राप्त करना होगा।
यदि आप Solidity के समान नेस्टेड मैपिंग को सिमुलेट (simulate) करने के लिए seeds का उपयोग करते हैं, तो आपको उसी प्रतिबंध का सामना करना पड़ेगा। आपको सभी seeds प्रदान करने होंगे — Solana केवल एक seed स्वीकार नहीं करेगा।
नेस्टेड मैपिंग को इनिशियलाइज़ करना: Rust
seeds एरे (array) में हम जितने चाहें उतने आइटम्स रख सकते हैं, ठीक वैसे ही जैसे Solidity में एक नेस्टेड मैपिंग में होता है। बेशक, यह प्रत्येक ट्रांज़ैक्शन पर लगाए गए कंप्यूट लिमिट्स (compute limits) के अधीन है। इनिशियलाइज़ेशन (initialization) और सेटिंग करने का कोड नीचे दिखाया गया है।
ऐसा करने के लिए हमें किसी विशेष सिंटैक्स की आवश्यकता नहीं है, यह बस अधिक फ़ंक्शन आर्गुमेंट्स लेने और seeds में अधिक आइटम्स डालने की बात है, इसलिए हम आगे किसी स्पष्टीकरण के बिना पूरा कोड दिखाएंगे।
Rust नेस्टेड मैपिंग
use anchor_lang::prelude::*;
use std::mem::size_of;
declare_id!("DntexDPByFxpVeBSjd6nLqQQSqZmSaDkP8TUbcJ9jAgt");
#[program]
pub mod example_map {
use super::*;
pub fn initialize(ctx: Context<Initialize>, key1: u64, key2: u64) -> Result<()> {
Ok(())
}
pub fn set(ctx: Context<Set>, key1: u64, key2: u64, val: u64) -> Result<()> {
ctx.accounts.val.value = val;
Ok(())
}
}
#[derive(Accounts)]
#[instruction(key1: u64, key2: u64)] // new key args added
pub struct Initialize<'info> {
#[account(init,
payer = signer,
space = size_of::<Val>() + 8,
seeds=[&key1.to_le_bytes().as_ref(), &key2.to_le_bytes().as_ref()], // 2 seeds
bump)]
val: Account<'info, Val>,
#[account(mut)]
signer: Signer<'info>,
system_program: Program<'info, System>,
}
#[derive(Accounts)]
#[instruction(key1: u64, key2: u64)] // new key args added
pub struct Set<'info> {
#[account(mut)]
val: Account<'info, Val>,
}
#[account]
pub struct Val {
value: u64,
}
Typescript नेस्टेड मैपिंग
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { ExampleMap } from "../target/types/example_map";
describe("example_map", () => {
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.ExampleMap as Program<ExampleMap>;
it("Initialize and set value", async () => {
// we now have two keys
const key1 = new anchor.BN(42);
const key2 = new anchor.BN(43);
const value = new anchor.BN(1337);
// seeds has two values
const seeds = [key1.toArrayLike(Buffer, "le", 8), key2.toArrayLike(Buffer, "le", 8)];
let valueAccount = anchor.web3.PublicKey.findProgramAddressSync(
seeds,
program.programId
)[0];
// functions now take two keys
await program.methods.initialize(key1, key2).accounts({val: valueAccount}).rpc();
await program.methods.set(key1, key2, value).accounts({val: valueAccount}).rpc();
// read the account back
let result = await program.account.val.fetch(valueAccount);
console.log(`the value ${result.value} was stored in ${valueAccount.toBase58()}`);
});
});
अभ्यास (Exercise): तीन कीज़ (keys) लेने वाली नेस्टेड मैपिंग बनाने के लिए उपरोक्त कोड को संशोधित (modify) करें।
एक से अधिक मैप को इनिशियलाइज़ करना
एक से अधिक मैप रखने का एक सीधा तरीका यह है कि seeds एरे में एक और वेरिएबल जोड़ा जाए और इसे पहले मैप, दूसरे मैप, इत्यादि को “इंडेक्स” (index) करने के तरीके के रूप में इस्तेमाल किया जाए।
निम्नलिखित कोड which_map को इनिशियलाइज़ करने का एक उदाहरण दिखाता है जो केवल एक की (key) रखता है।
#[derive(Accounts)]
#[instruction(which_map: u64, key: u64)]
pub struct InitializeMap<'info> {
#[account(init,
payer = signer,
space = size_of::<Val1>() + 8,
seeds=[&which_map.to_le_bytes().as_ref(), &key.to_le_bytes().as_ref()],
bump)]
val: Account<'info, Val1>,
#[account(mut)]
signer: Signer<'info>,
system_program: Program<'info, System>,
}
अभ्यास (Exercise): एक ऐसा प्रोग्राम बनाने के लिए Rust और Typescript कोड को पूरा करें जिसमें दो मैपिंग हों: पहली एक सिंगल की (single key) के साथ और दूसरी दो कीज़ (two keys) के साथ। इसके बारे में सोचें कि जब पहला मैप निर्दिष्ट (specified) हो तो दो लेवल वाले मैप (two level map) को सिंगल लेवल वाले मैप (single level map) में कैसे बदला जाए।
RareSkills के साथ Solana सीखें
हमारे बाकी Solana ट्यूटोरियल्स देखने के लिए हमारा Solana कोर्स देखें।
मूल रूप से 27 फरवरी, 2024 को प्रकाशित