在Solana中创建“映射”和“嵌套映射”

本文详细介绍了如何在 Solana 中使用 seeds 参数来实现类似于 Solidity 中的映射和嵌套映射,并提供了 Rust 和 Typescript 的代码示例。

"Mappings" 和 "Nested Mappings" 在 Solana 中

在之前的教程中,seeds=[] 参数总是为空。如果我们向其中放入数据,它的表现就像 Solidity 映射中的一个或多个键。

考虑以下示例:

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

起初,我们只会展示初始化步骤,因为它将引入一些需要解释的新语法。

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 可以看作是一个类似于 Solidity 构造形式的“键”:

mapping(uint256 => uint256) myMap;
myMap[key] = val

代码中不熟悉的部分是 #[instruction(key: u64)]seeds=[&key.to_le_bytes().as_ref()]

seeds = [&key.to_le_bytes().as_ref()]

seeds 中的项预期是字节。然而,我们传入的是一个 u64,它不是字节类型。为了将其转换为字节,我们使用 to_le_bytes()。“le” 是指 “小端”。seeds 不必编码为小端字节,我们只是在这个例子中选择了它。只要你保持一致,大端也可以使用。如果要转换为大端,我们将使用 to_be_bytes()

[instruction(key: u64)]

为了在 initialize(ctx: Context<Initialize>, key: u64) 中“传递”参数 key,我们需要使用 instruction 宏,否则我们的 init 宏没有办法“看到”来自 initializekey 参数。

初始化映射: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) 指定我们试图使用来自 key 的值创建一个大小为 8 字节的字节缓冲区。我们选择 8 字节,因为我们的键是 64 位,64 位是 8 字节。“le” 是小端,以便与 Rust 代码匹配。

映射中的每个“值”都是一个单独的账户,必须单独初始化。

设置映射:Rust

我们需要的额外 Rust 代码来设置值。这里的语法应该是熟悉的。

// 在 #[program] 模块内
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

因为我们在客户端(Typescript)推导出存储值的账户地址,所以我们就像处理 seeds 数组为空的账户那样读取和写入它。读取和写入 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();

    // 设置账户
    await program.methods.set(key, value).accounts({val: valueAccount}).rpc();

    // 读取账户
    let result = await program.account.val.fetch(valueAccount);

    console.log(`值 ${result.value} 被存储在 ${valueAccount.toBase58()}`);
  });
});

澄清“嵌套映射”

在像 Python 或 JavaScript 这样的语言中,真正的嵌套映射是一个指向另一个哈希图的哈希图。

然而,在 Solidity 中,“嵌套映射”只是一个拥有多个键的单一映射,表现得就像它们是一个键。

在“真正的”嵌套映射中,你可以只提供第一个键并返回另一个哈希图。

Solidity 的“嵌套映射”不是“真正的”嵌套映射:你不能提供一个键并获得一个映射返回:你必须提供所有的键才能得到最终结果。

如果你使用 seeds 来模拟类似于 Solidity 的嵌套映射,你将面临相同的限制。你必须提供所有的 seeds——Solana 不会接受只有一个 seed。

初始化嵌套映射:Rust

seeds 数组可以容纳任意数量的项,这类似于 Solidity 中的嵌套映射。当然,它受到每笔交易施加的计算限制。下面显示了初始化和设置的代码。

我们不需要任何特殊的语法,只是需要更多的函数参数并在 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)] // 添加的新键参数
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)] // 添加的新键参数
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 () => {
    // 我们现在有两个键
    const key1 = new anchor.BN(42);
    const key2 = new anchor.BN(43);
    const value = new anchor.BN(1337);

    // seeds 现在有两个值
    const seeds = [key1.toArrayLike(Buffer, "le", 8), key2.toArrayLike(Buffer, "le", 8)];
    let valueAccount = anchor.web3.PublicKey.findProgramAddressSync(
      seeds,
      program.programId
    )[0];

    // 函数现在使用两个键
    await program.methods.initialize(key1, key2).accounts({val: valueAccount}).rpc();
    await program.methods.set(key1, key2, value).accounts({val: valueAccount}).rpc();

    // 读取账户
    let result = await program.account.val.fetch(valueAccount);
    console.log(`值 ${result.value} 被存储在 ${valueAccount.toBase58()}`);
  });
});

练习 : 修改上述代码以形成一个使用三个键的嵌套映射。

初始化多个映射

实现多个映射的一种简单方法是向 seeds 数组中添加另一个变量,并将其视为“索引”第一个映射、第二个映射等等。

以下代码展示了初始化 which_map 的示例,该映射仅持有一个键。

#[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>,
}

练习 : 完成 Rust 和 Typescript 代码以创建一个具有两个映射的程序:第一个映射使用一个键,第二个映射使用两个键。考虑如何在指定第一个映射时,将两个级别的映射转换为单级映射。

与 RareSkills 一起学习 Solana

请查看我们的 Solana 课程 以查看其余的 Solana 教程。

原发布时间:2024年2月27日

  • 原文链接: rareskills.io/post/solan...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
RareSkills
RareSkills
https://www.rareskills.io/