您正在查看: Surou 发布的文章

Solana基础 - Solana中的Require、Revert和自定义错误

文章详细介绍了在 Solana 的 Anchor 框架中如何处理函数参数的限制,类似于以太坊中的 require 语句。通过代码示例展示了如何使用 require! 宏和错误处理机制来确保函数参数的有效性,并解释了 Solana 和以太坊在错误处理上的差异。

在以太坊中,我们经常会看到一个 require 语句来限制函数参数可以接受的值。考虑以下示例:

function foobar(uint256 x) public {
    require(x < 100, "I'm not happy with the number you picked");
    // 其余的函数逻辑
}

在上面的代码中,如果 foobar 被传递一个 100 或更大的值,交易将会回退。

我们如何在 Solana 中实现这一点,或者更具体地说,在 Anchor 框架中实现?

Anchor 对 Solidity 的自定义错误和 require 语句有等效的实现。它们的 文档 在这一主题上相当不错,但我们也将解释如何在函数参数不符合预期时停止交易。

下面的 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", () => {
  // 配置客户端以使用本地集群。
  anchor.setProvider(anchor.AnchorProvider.env());

  const program = anchor.workspace.Day4 as Program<Day4>;

  it("Input test", async () => {
    // 在此添加测试。
    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);
    }
  });
});

练习

  1. 你注意到错误编号有什么模式?如果你改变 enum MyError 中错误的顺序,错误代码会发生什么?
  2. 使用这段代码块,将新函数和错误添加到现有代码中:
#[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(())
    }

    // 新函数
    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")]  // 新错误,你认为错误代码是什么?
    AlwaysErrors,
}

并添加这个测试:

it("Error test", async () => {
    // 在此添加测试。
    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);
    }
  });

在你运行这个之前,你认为新的错误代码将是什么?

以太坊和 Solana 在处理无效参数的交易停止方式的显著区别是,Ethereum 触发回退,而 Solana 返回一个错误。

使用 require 语句

有一个 require! 宏,概念上与 Solidity 的 require 相同,我们可以用它来简化我们的代码。将 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(())
}

在以太坊中,我们知道如果函数回退,则不会记日志,即使回退是在记录之后发生。例如,下面合约中的 tryToLog 调用不会记录任何内容,因为函数回退:

contract DoesNotLog {
    event SomeEvent(uint256);

    function tryToLog() public {
        emit SomeEvent(100);
        require(false);
    }
}

练习:如果你在 Solana 程序函数中返回错误语句之前放置 msg! 宏,结果会怎么样?如果你用 Ok(()) 替代 return err!,结果会怎么样?下面的函数在返回错误时先记录一些信息,看看 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,
}

在底层,require! 宏与返回错误没有什么不同,它只是语法糖。

预期结果是,当你返回 Ok(()) 时,“Will this print?” 会被打印,而当你返回错误时则不会打印。

关于 Solana 和 Solidity 在处理错误方面的区别

在 Solidity 中,require 语句通过 revert 操作码停止执行。Solana 并不会停止执行,而是简单返回一个不同的值。这类似于操作系统如何在成功时返回 0 或 1。如果返回了 0(相当于返回 Ok(())),那么一切顺利。

因此,Solana 程序应该始终返回某个值——要么是 Ok(()) 或者是 Error

在 Anchor 中,错误是具有 #[error_code] 属性的枚举。

请注意,Solana 中的所有函数返回类型都是 Result<()>。一个 result 是一种可能是 Ok(()) 或错误的类型。

问题与解答

为什么 Ok(()) 结尾没有分号?

如果你添加它,你的代码将无法编译。如果 Rust 中最后一条语句没有分号,那么该行的值会被返回。

为什么 Ok(()) 有一对额外的括号?

() 在 Rust 中表示“单位”,你可以将其视为 C 中的 void 或 Haskell 中的 Nothing。这里,Ok 是包含单位类型的枚举。这就是返回的内容。在 Rust 中,不返回任何内容的函数隐式返回单位类型。没有分号的 Ok(())return Ok(()) 在语法上是等价的。请注意末尾的分号。

上述 if 语句为何缺少括号?

在 Rust 中,这些是可选的。

转载:https://learnblockchain.cn/article/11401

Solana基础 - Solana Anchor 程序 IDL

本文详细介绍了 Solana 如何使用 IDL(接口定义语言)来描述如何与 Solana 程序交互,并通过 Anchor 框架自动生成 IDL 文件。文章还展示了如何通过 Rust 编写 Solana 程序,并通过 TypeScript 单元测试进行验证。

IDL(接口定义语言)是一个描述如何与Solana程序交互的JSON文件。它是由Anchor框架自动生成的。

名为“initialize”的函数没有什么特别之处——这是Anchor选择的一个名称。在本教程中,我们将学习Typescript单元测试如何能够“找到”适当的函数。

让我们创建一个新的项目,名为anchor-function-tutorial,并将initialize函数中的名称更改为boaty_mc_boatface,保持其他一切不变。

pub fn boaty_mc_boatface(ctx: Context<Initialize>) -> Result<()> {
    Ok(())
}

现在让我们将测试更改为如下:

it("调用boaty mcboatface", async () => {
  // 在这里添加你的测试。
  const tx = await program.methods.boatyMcBoatface().rpc();
  console.log("你的交易签名", tx);
});

现在运行测试 anchor test --skip-local-validator

它按预期运行。那么这个魔法是如何工作的呢?

测试如何知道initialize函数?

当Anchor构建Solana程序时,它会创建一个IDL(接口定义语言)。

它存储在target/idl/anchor_function_tutorial.json中。这个文件被称为anchor_function_tutorial.json是因为anchor_function_tutorial是程序的名称。请注意,Anchor将连字符转换为下划线!

让我们打开它。

{
  "version": "0.1.0",
  "name": "anchor_function_tutorial",
  "instructions": [
    {
      "name": "boatyMcBoatface",
      "accounts": [],
      "args": []
    }
  ]
}

“instructions”的列表是该程序支持的公开函数,粗略上等同于以太坊合约中的外部和公共函数。Solana中的IDL文件在与合约的交互方式上,与Solidity中的ABI文件具有相似的角色。

我们之前看到函数不接受任何参数,这就是为什么args列表为空。稍后我们将解释“accounts”是什么。

一个显著的区别是:Rust中的函数是蛇形命名的,但Anchor在JavaScript中将它们格式化为驼峰命名。这是为了尊重这些语言的命名约定:Rust倾向于使用蛇形命名,而JavaScript通常使用驼峰命名。

这个JSON文件是“methods”对象知道支持哪些函数的方式。

当我们运行测试时,我们期望它通过,这意味着该测试正确地调用了Solana程序:

运行Solana测试套件

练习:boaty_mc_boatface函数添加一个接收u64的参数。再次运行anchor build。然后再次打开target/idl/anchor_function_tutorial.json文件。它会改变吗?

现在让我们开始创建一个Solana程序,该程序具有基本加法和减法的函数并打印结果。Solana函数不能像Solidity那样返回值,因此我们将不得不打印它们。(Solana有其他方法传递值,以后我们会讨论这些)。让我们创建两个函数,像这样:

pub fn add(ctx: Context<Initialize>, a: u64, b: u64) -> Result<()> {
  let sum = a + b;
  msg!("和是 {}", sum);  
    Ok(())
}

pub fn sub(ctx: Context<Initialize>, a: u64, b: u64) -> Result<()> {
  let difference = a - b;
  msg!("差是 {}", difference);  
    Ok(())
}

并将我们的单元测试更改为如下:

it("应该加法", async () => {
  const tx = await program.methods.add(new anchor.BN(1), new anchor.BN(2)).rpc();
  console.log("你的交易签名", tx);
});

it("应该减法", async () => {
  const tx = await program.methods.sub(new anchor.BN(10), new anchor.BN(3)).rpc();
  console.log("你的交易签名", tx);
});

练习:muldivmodulo实现类似的函数,并编写单元测试以触发每一个。

那么Initialize结构体呢?

现在还有一个狡猾的事情发生。我们已经保持Initialize结构体不变,并在函数间重新使用它。同样,名称并不重要。让我们将结构体名称更改为Empty并重新运行测试。

//...
  // 在此处更改结构体名称
    pub fn add(ctx: Context<Empty>, a: u64, b: u64) -> Result<()> {
        let sum = a + b;
        msg!("和是 {}", sum);
        Ok(())
    }
//...

// 在这里也更改结构体名称
#[derive(Accounts)]
pub struct Empty {}

再次强调,名称Empty在这里完全是任意的。

练习: 将结构体名称Empty更改为BoatyMcBoatface并重新运行测试。

什么是#[derive(Accounts)]结构体?

这个#语法是Anchor框架定义的Rust属性。我们将在后面的教程中对此进行进一步说明。现在,我们想关注IDL中的accounts键以及它与程序中定义的结构体之间的关系。

Accounts IDL键

下面我们截图展示以上程序的IDL。所以我们可以看到Rust属性#[derive(Accounts)]中的“Accounts”和IDL中的“accounts”键之间的关系:

IDL的截图

在我们的例子中,上面JSON IDL中的accounts键(用紫色箭头标记)是空的。但对于大多数有用的Solana事务,这并不是这样,稍后我们将学习。

由于BoatyMcBoatface的账户结构体为空,因此IDL中的账户列表也为空。

现在让我们看看当结构体非空时发生什么。复制下面的代码并替换lib.rs中的内容。

use anchor_lang::prelude::*;

declare_id!("8PSAL9t1RMb7BcewhsSFrRQDq61Y7YXC5kHUxMk5b39Z");

#[program]
pub mod anchor_function_tutorial {
    use super::*;

    pub fn non_empty_account_example(ctx: Context<NonEmptyAccountExample>) -> Result<()> {
        Ok(())
    }
}

#[derive(Accounts)]
pub struct NonEmptyAccountExample<'info> {
    signer: Signer<'info>,
    another_signer: Signer<'info>,
}

现在运行anchor build——让我们看看返回的新IDL。

{
  "version": "0.1.0",
  "name": "anchor_function_tutorial",
  "instructions": [
    {
      "name": "nonEmptyAccountExample",
      "accounts": [
        {
          "name": "signer",
          "isMut": false,
          "isSigner": true
        },
        {
          "name": "anotherSigner",
          "isMut": false,
          "isSigner": true
        }
      ],
      "args": []
    }
  ],
  "metadata": {
    "address": "8PSAL9t1RMb7BcewhsSFrRQDq61Y7YXC5kHUxMk5b39Z"
  }
}

请注意“accounts”不再为空,而是用结构体中的字段“signer”和“anotherSigner”填充。(请注意,another_signer从蛇形命名转换为驼峰命名)。IDL已经更新以匹配我们刚刚更改的结构体,特别是我们添加的账户数量。

我们将在后续教程中进一步深入“Signer”的内容,但现在你可以将其视为与以太坊中的tx.origin的类似物。

另一个程序和IDL示例。

为了总结我们到目前为止学到的一切,让我们构建另一个具有不同函数和账户结构体的程序。

use anchor_lang::prelude::*;

declare_id!("8PSAL9t1RMb7BcewhsSFrRQDq61Y7YXC5kHUxMk5b39Z");

#[program]
pub mod anchor_function_tutorial {
    use super::*;

    pub fn function_a(ctx: Context<NonEmptyAccountExample>) -> Result<()> {
        Ok(())
    }

    pub fn function_b(ctx: Context<Empty>, firstArg: u64) -> Result<()> {
        Ok(())
    }
}

#[derive(Accounts)]
pub struct NonEmptyAccountExample<'info> {
    signer: Signer<'info>,
    another_signer: Signer<'info>,
}

#[derive(Accounts)]
pub struct Empty {}

现在用anchor build构建它

让我们再次查看IDL文件target/idl/anchor_function_tutorial.json,并将这些文件并排放置:

IDL文件截图结果

你能看到IDL文件与上面的程序之间的关系吗?

函数function_a没有参数,这在IDL中显示为args键下的空数组。

它的Context采用NonEmptyAccountExample结构体。该结构体NonEmptyAccountExample有两个签名字段:signeranother_signer。请注意,这些在IDL的function_a的accounts键中重复作为元素。你可以看到Anchor将Rust的蛇形命名转换为IDL中的驼峰命名。

Anchor 0.30更新 Anchor不再自动执行此转换(发布说明)。

函数function_b接受一个u64参数。它的上下文结构体是空的,因此function_b在IDL中的accounts键是一个空数组。

通常,我们期望IDL的accounts键中的项数组与函数在其ctx参数中采用的账户结构体的键匹配。

总结

在本章中:

  • 我们了解到Solana使用IDL(接口定义语言)来显示如何与Solana程序交互以及IDL中出现的字段。
  • 我们介绍了通过#[derive(Accounts)]修改的结构体以及它如何与函数参数相关联。
  • Anchor将Rust中的snake_case函数转化为Typescript测试中的camelCase函数。

转载:https://learnblockchain.cn/article/11414

Solana基础 - Solana 和 Rust 中的算术与基本类型

本文详细介绍了如何在Solana平台上构建程序,其功能与Solidity合约类似,并探讨了Solana如何处理算术溢出问题。文章通过具体示例展示了如何在Rust中实现函数,处理数据类型,以及进行单元测试,同时强调了计算成本及浮点操作的性能限制。
今天我们将学习如何创建一个Solana程序,该程序实现与下面的Solidity合约相同的功能。我们还将学习Solana如何处理整数溢出等算术问题。

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测试验证器:

solana-test-validator

在另一个终端运行Solana日志:

solana logs

通过运行测试确保新搭建的程序正常工作

anchor test --skip-local-validator

提供函数参数

在我们进行任何数学计算之前,让我们将初始化函数更改为接收两个整数。以太坊使用uint256作为“标准”整数大小。在Solana上,它是u64——这相当于Solidity中的uint64。

传递无符号整数

默认的初始化函数将如下所示:

pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
    Ok(())
}

在lib.rs中修改initialize()函数,如下所示。

pub fn initialize(ctx: Context<Initialize>,
                  a: u64,
                  b: u64) -> Result<()> {
    msg!("你发送了 {} 和 {}", a, b);
    Ok(())
}

现在我们需要更改./tests/day2.ts中的测试。

it("初始化成功!", async () => {
  // 在这里添加你的测试。
  const tx = await program.methods
    .initialize(new anchor.BN(777), new anchor.BN(888)).rpc();
  console.log("你的交易签名", tx);
});

现在重新运行anchor test --skip-local-validator

当我们查看日志时,应该看到以下内容

Transaction executed in slot 367357:
  Signature: 54iJFbtEE61T9X2WCLbMe8Dq2YYBzCLYE4qW2DqTsA4gZRgootcubLgHc1MHYncbP63sxNxEY8tJfgfgsdt1Ch4g
  Status: Ok
  Log Messages:
    Program 8o3ehd3XnyDocd9hG1uz5trbmSRB7gaLaE9BCXDpEnMY invoke [1]
    Program log: Instruction: Initialize
    Program log: 你发送了 777 和 888
    Program 8o3ehd3XnyDocd9hG1uz5trbmSRB7gaLaE9BCXDpEnMY 消耗了 1116 的 200000 计算单位
    Program 8o3ehd3XnyDocd9hG1uz5trbmSRB7gaLaE9BCXDpEnMY 成功

传递字符串

现在让我们演示如何将字符串作为参数传递。

pub fn initialize(ctx: Context<Initialize>,
                  a: u64,
                  b: u64,
                  message: String) -> Result<()> {
    msg!("你说了 {:?}", message);
    msg!("你发送了 {} 和 {}", a, b);
    Ok(())
}

并更改测试。

it("初始化成功!", async () => {
  // 在这里添加你的测试。
  const tx = await program.methods
    .initialize(
       new anchor.BN(777), new anchor.BN(888), "hello").rpc();
    console.log("你的交易签名", tx);
});

当我们运行测试时,会看到新的日志。

数字数组

接下来,我们添加一个函数(和测试)来演示如何传递数字数组。在Rust中,“向量”或Vec就是Solidity所称的“数组”。

pub fn initialize(ctx: Context<Initialize>,
                  a: u64,
                  b: u64,
                  message: String) -> Result<()> {
    msg!("你说了 {:?}", message);
    msg!("你发送了 {} 和 {}", a, b);
    Ok(())
}

// 添加这个函数
pub fn array(ctx: Context<Initialize>,
             arr: Vec<u64>) -> Result<()> {
    msg!("你的数组 {:?}", arr);
    Ok(())
}

我们将单元测试更新如下

it("初始化成功!", async () => {
  // 在这里添加你的测试。
  const tx = await program.methods.initialize(new anchor.BN(777), new anchor.BN(888), "hello").rpc();
  console.log("你的交易签名", tx);
});

// 添加这个测试
it("数组测试", async () => {
  const tx = await program.methods.array([new anchor.BN(777), new anchor.BN(888)]).rpc();
  console.log("你的交易签名", tx);
});

然后我们再次运行测试并查看日志,以查看数组输出:

Transaction executed in slot 368489:
  Signature: 3TBzE3NddEY8KREv1FSXnieoyT6G6iNxF1n4hJHCeeWhAsUward3MEKm9WJHV4PMjPxeN2jRSRC9Rq8FUKjXoBQR
  Status: Ok
  Log Messages:
    Program 8o3ehd3XnyDocd9hG1uz5trbmSRB7gaLaE9BCXDpEnMY invoke [1]
    Program log: Instruction: Initialize
    Program log: 你说了 [777, 888]
    Program 8o3ehd3XnyDocd9hG1uz5trbmSRB7gaLaE9BCXDpEnMY 消耗了 1587 的 200000 计算单位
    Program 8o3ehd3XnyDocd9hG1uz5trbmSRB7gaLaE9BCXDpEnMY 成功

提示:如果你在Anchor测试中遇到问题,请尝试在Google中搜索与该错误相关的“Solana web3 js”。Anchor使用的TypeScript库是Solana Web3 JS库。

Solana中的数学

浮点数学

Solana有一些有限的原生支持浮点运算。

然而,最好避免浮点运算,因为它们计算消耗很大(稍后我们将看到这一点的例子)。请注意,Solidity对浮点运算没有原生支持。

关于使用浮点数的限制请阅读这里

算术溢出

算术溢出在Solidity 0.8.0版本之前是一种常见的攻击途径,该版本默认内建了溢出保护。在Solidity 0.8.0或更高版本中,溢出检查是默认进行的。由于这些检查会消耗gas,因此有时开发人员会故意通过“unchecked”块来禁用它们。

Solana如何防止算术溢出?

方法1:在Cargo.toml中设置overflow-checks = true

如果在Cargo.toml文件中将键overflow-checks设置为true,则Rust将在编译器级别添加溢出检查。请查看下一步Cargo.toml的截图:

cargo.toml中的overflow-checks键设置为true

如果Cargo.toml文件是这样配置的,你就不需要担心溢出。

然而,添加溢出检查会增加交易的计算成本(我们稍后将对此进行重访)。因此在一些计算成本问题影响下,你可能希望将overflow-checks设置为false。要战略性地检查溢出,可以使用Rust中的checked_*运算符。

方法2:使用checked_*运算符。

让我们看看如何在Rust自身的算术运算中应用溢出检查。考虑下面的Rust代码片段。

  • 在第1行,我们使用通常的+运算符进行算术运算,它会静默溢出。
  • 在第2行,我们使用.checked_add,如果发生溢出则会抛出错误。请注意,我们还可以对其他操作使用.checked_*,如checked_subchecked_mul
let x: u64 = y + z; // 将静默溢出
let xSafe: u64 = y.checked_add(z).unwrap(); // 如果发生溢出,将panic

// checked_sub、checked_mul等也可用

练习1:设置overflow-checks = true,创造一个测试案例,通过做0 - 1来使u64发生下溢。你需要将这些数字作为参数传递,否则代码将无法编译。会发生什么?

你会看到,当测试运行时交易失败(附带一个相当隐晦的错误信息),因为Anchor启动了溢出保护:

练习1错误信息

练习2:现在将overflow-checks设置为false,然后再次运行测试。你应该看到下溢值为18446744073709551615。

练习3:在Cargo.toml中禁止溢出保护,进行let result = a.checked_sub(b).unwrap();,同时让a = 0b = 1。会发生什么?

你是否应该将overflow-checks = true保持在Cargo.toml文件中用于你的Anchor项目?通常是的。但是如果你正在进行一些复杂计算,你可能想将overflow-checks设置为false,并在关键节点上战略性地防御溢出以节省计算成本,接下来我们将演示这一点。

Solana计算单位101

在以太坊中,交易运行直到消耗交易指定的“gas限额”。Solana称“gas”为“计算单位”。默认情况下,交易限制为200,000计算单位。如果消耗超过200,000计算单位,该交易会回滚。

确定Solana中交易的计算成本

与以太坊相比,Solana确实便宜使用,但这并不意味着你在以太坊开发中的优化技能毫无用处。让我们测量一下我们的数学函数需要多少计算单位。

Solana日志终端还显示所使用的计算单位。我们为checked和unchecked减法提供了基准测试。

禁用溢出保护时消耗824计算单位:

禁用溢出保护的Solana日志

启用溢出保护时消耗872计算单位:

启用溢出保护的Solana日志

正如你所看到的,进行一个简单的数学运算将使用近1000个单位。由于我们有20万单位,我们每笔交易只能够执行几百个简单的算术操作。因此,尽管Solana上的交易通常比以太坊便宜,但我们仍然受到相对较小的计算单位上限的限制,并且无法在Solana链上执行复杂的计算任务,比如流体动力学模拟。

我们稍后会重访交易成本。

Power不使用与Solidity相同的语法

在Solidity中,如果我们想将x提高到y的幂,我们这样写:

uint256 result = x ** y;

Rust不使用这种语法。相反地,它使用.pow

let x: u64 = 2; // 基数的数据类型必须明确
let y = 3; // 指数的数据类型可以推断
let result = x.pow(y);

如果你担心溢出,还有.checked_pow

浮点数

使用Rust进行智能合约开发的一个好处是,我们不必导入像Solmate或Solady这样的库来进行数学运算。Rust是一个相当复杂的语言,内置了许多操作,如果我们需要一些代码,我们可以在Solana生态系统外寻找一个Rust crate(这是Rust中库的称谓)来完成这个任务。

让我们计算50的立方根。浮点数的立方根函数已内置在Rust语言中,使用函数cbrt()

// 注意我们将`a`更改为f32(浮点32)
// 因为`cbrt()`对u64不可用
pub fn initialize(ctx: Context<Initialize>, a: f32) -> Result<()> {
  msg!("你说了 {:?}", a.cbrt());
  Ok(());
}

还记得我们在前面的部分提到过浮点数计算可能非常耗费计算资源吗?好吧,在这里,我们看到我们的立方根操作消耗的计算单位超过了简单无符号整数算术的5倍以上:

Transaction executed in slot unspecified:
  Signature: VfvySG5vvVSAnsYLCsvB9N6PsuGwL39kKd1fMsyvuB7y5DUHURwQVHU9rv3Xkz5NJqGHLSXoWoW92zJb5VKYCEF
  Status: Ok
  Log Messages:
    Program 8o3ehd3XnyDocd9hG1uz5trbmSRB7gaLaE9BCXDpEnMY invoke [1]
    Program log: Instruction: Initialize
    Program log: 试图开始函数,参数为50
    Program log: 结果 = 3.6840315
    Program 8o3ehd3XnyDocd9hG1uz5trbmSRB7gaLaE9BCXDpEnMY 消耗了 4860 的 200000 计算单位
    Program 8o3ehd3XnyDocd9hG1uz5trbmSRB7gaLaE9BCXDpEnMY 成功

练习4:构建一个实现+、-、×和÷运算的计算器,并包含sqrt和log10运算。

转载:https://learnblockchain.cn/article/11403

Solana基础 - 开始 Solana - 安装与故障排除

本文详细介绍了如何安装 Solana 开发环境,并提供了一个从安装到运行“Hello World”程序的完整教程,包含问题排查和常见错误的解决方案。
这是一个 Solana 的 Hello World 教程。我们将指导你完成安装 Solana 的步骤并解决可能出现的问题。

如果你遇到问题,请查看本文末尾的故障排除部分。

安装步骤

安装 Rust

如果你已安装 Rust,请跳过此步骤。

# 安装 rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

安装 Yarn

你将需要它来运行单元测试。如果你已安装 yarn,请跳过此步骤。

# 安装 yarn -- 假设已安装 node js
corepack enable # corepack 随 node js 一起提供

安装 Solana cli

我们强烈建议使用 stable 版本,而不是 latest。Solana 的安装不再支持符号通道名称(edgebetastable),因此我们必须指定版本。

# 安装 solana
sh -c "$(curl -sSfL https://release.solana.com/v1.16.25/install)"

安装 Anchor

Anchor 是 Solana 开发的框架。在很多方面,它与 hardhat 非常相似。

# 安装 anchor
cargo install --git https://github.com/coral-xyz/anchor avm --locked --force

avm install latest
avm use latest

测试安装

初始化并构建 Anchor 程序(用于 hello world)

Mac 用户: 我们建议将你的程序命名为 day_1 而不是 day1,因为在 mac 机器上,Anchor 有时会默默插入下划线。

anchor init day1 # 如果你使用的是 mac,请使用 day_1
cd day1
anchor build

根据你的机器和互联网连接,这一步可能需要一段时间。这一步也是你最有可能遇到安装问题的地方,因此如果有必要,请查看故障排除部分。

配置 Solana 以在localhost上运行

# shell 1

solana config set --url localhost

运行测试验证节点

在新的 shell 中运行以下命令,而不是在 Anchor 项目中。但不要关闭你运行 anchor build 的 shell。这是在你的机器上运行本地(测试)Solana 节点实例:

# shell 2

solana-test-validator

确保 program_id 与 Anchor 密钥同步

返回到 Anchor 项目的 shell,运行以下命令:

# shell 1

anchor keys sync

运行测试

在 Anchor 项目中运行:

# shell 1

anchor test --skip-local-validator

上面的命令为我们的程序运行测试。如果你尚未创建测试钱包,Anchor 会给你提供如何创建钱包的说明。我们在这里不提供这些说明,因为它将依赖于你的操作系统和文件结构。你还可能需要通过在终端运行 solana airdrop 100 {YOUR_WALLET_ADDRESS} 来为自己空投一些本地 Sol。你可以通过在命令行中运行 solana address 获取你的钱包地址。

预期输出如下:

Successful test run

Hello World

现在让我们让程序输出“Hello, world!”将以下行标记为 ****NEW LINE HERE**** 添加到 programs/day_1/src/lib.rs

use anchor_lang::prelude::*;

declare_id!("...");

#[program]
pub mod day_1 {
    use super::*;

    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
        msg!("Hello, world!"); // **** NEW LINE HERE ****
        Ok(())
    }
}

#[derive(Accounts)]
pub struct Initialize {}

在再次运行测试之前,请结束本地验证器进程并使用以下命令重新启动:

solana-test-validator --reset

再次运行测试

anchor test --skip-local-validator

通过运行找到日志文件

ls .anchor/program-logs/

打开该文件以查看记录的“Hello world”

Program log "Hello world"

实时 Solana 日志

另外,你可以通过打开第三个 shell 并运行以下命令来查看实时日志:

# shell 3

solana logs

现在再次运行测试,你应该会在运行 solana logs 的终端中看到相同的信息。

问题与解答

为什么 declare_id! 和 msg! 后面有感叹号?

在 Rust 中,感叹号表示这些是宏。我们将在以后的教程中再次讨论宏。

我需要 initialize 函数吗?

不,这个是 Anchor 框架自动生成的。你可以将其命名为其他任何名称。

在这种情况下,initialize 这个名称没有什么特别之处,因此我们可以将其更改为我们喜欢的任何名称。这与某些其他关键词和语言不同,比如在某些语言中,main 是一个特殊名称,或者在 Solidity 中构造函数是一个特殊名称。

练习尝试将 programs/day_1/src/lib.rstests/day_1.ts 中的 initialize 重命名为 initialize2 并再次运行测试。见以下橙色圆圈标记的变化。
Renaming excersise

为什么我们使用 --skip-local-validator 运行测试?

当测试针对节点运行时,我们将能够查询节点的状态更改。如果你无法使节点运行,可以不带 --skip-local-validator 标志直接运行 anchor test。但是,你将在开发和测试时遇到更多困难,因此我们建议使本地验证器正常工作。

故障排除

Solana 是一个快速发展的软件,你可能会遇到安装问题。我们已经记录了你最可能遇到的问题,以下是相关部分。

我们的教程系列是用以下版本编写的: Anchor = 版本 0.29.0 Solana = 版本 1.16.25 * Rustc = 1.77.0-nightly

你可以通过运行以下命令更改 Anchor 版本:

avm install 0.29.0
avm use 0.29.0

error: package solana-program v1.18.0 cannot be built

error: package `solana-program v1.18.0` cannot be built because it requires rustc 1.72.0 or newer, while the currently active rustc version is 1.68.0-dev
Either upgrade to rustc 1.72.0 or newer, or use
cargo update -p solana-program@1.18.0 --precise ver

使用 solana --version 检查你当前使用的 Solana 版本。然后将该版本插入 ver 上面。下面显示了一个解决方案示例:

Check Solana version

error[E0658]: use of unstable library feature ‘build_hasher_simple_hash_one’

如果你收到以下错误:

error[E0658]: use of unstable library feature 'build_hasher_simple_hash_one'
--> src/random_state.rs:463:5
|
463 | / fn hash_one<T: Hash>(&self, x: T) -> u64 {
464 | | RandomState::hash_one(self, x)
465 | | }
| |_____^
|
= note: see issue #86161 https://github.com/rust-lang/rust/issues/86161 for more information
= help: add #![feature(build_hasher_simple_hash_one)] to the crate attributes to enable

运行以下命令:cargo update -p ahash@0.8.7 --precise 0.8.6

来源 : https://solana.stackexchange.com/questions/8800/cant-build-hello-world

错误:部署程序失败:处理指令 1 时出错:自定义程序错误:0x1

Error: Deploying program failed: Error processing Instruction 1: custom program error: 0x1
There was a problem deploying: Output { status: ExitStatus(unix_wait_status(256)), stdout: "", stderr: "" }.

如果你收到此错误,说明你的密钥未同步。运行 anchor keys sync

错误:发送交易失败:交易模拟失败:尝试加载不存在的程序

你的密钥未同步。运行 anchor keys sync

错误:你配置的 rpc 端口:8899 已在使用中

你在验证器在后台运行时,运行了不带 --skip-local-validatoranchor test。请关闭验证器并运行 anchor test,或在运行验证器的情况下运行 anchor test --skip-local-validator。跳过本地验证器意味着跳过为项目创建的临时验证器,而不是在后台运行的那个。

错误:账户 J7t…zjK 资金不足

运行下面的命令向你的开发地址空投 100 SOL:

solana airdrop 100 J7t...zjK

错误:RPC 请求错误:集群版本查询失败

Error: RPC request error: cluster version query failed: error sending request for url (http://localhost:8899/): error trying to connect: tcp connect error: Connection refused (os error 61)
There was a problem deploying: Output { status: ExitStatus(unix_wait_status(256)), stdout: "", stderr: "" }.

这意味着 solana-test-validator 并未在后台运行。在另一个 shell 中运行 solana-test-validator

线程 'main' 在 '调用 Option::unwrap() 时' 发生恐慌:None

thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', /Users/username/.cargo/git/checkouts/anchor-50c4b9c8b5e0501f/347c225/lang/syn/src/idl/file.rs:214:73
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

你可能还没有运行 anchor build

我使用的是 Mac,并且我收到错误:无法启动验证器:在 test-ledger 上创建分类账失败:块存储错误

请按照这个 Stack Exchange 线程 中的说明进行操作。

尽管我有 node.js,但 Mac 上没有 corepack

运行以下命令:

brew install corepack
brew link --overwrite corepack

来源 : https://stackoverflow.com/questions/70082424/command-not-found-corepack-when-installing-yarn-on-node-v17-0-1

error: 不是目录:

BPF SDK: /Users/rareskills/.local/share/solana/install/releases/stable-43daa37937907c10099e30af10a5a0b43e2dd2fe/solana-release/bin/sdk/bpf
cargo-build-bpf child: rustup toolchain list -v
cargo-build-bpf child: rustup toolchain link bpf /Users/rareskills/.local/share/solana/install/releases/stable-43daa37937907c10099e30af10a5a0b43e2dd2fe/solana-release/bin/sdk/bpf/dependencies/bpf-tools/rust
error: not a directory:

清除缓存:运行 rm -rf ~/.cache/solana/*

错误:target/idl/day_1.json 不存在。你是否运行了 anchor build

创建一个新项目并命名为 day_1,而不是 day1。Anchor 在某些机器上似乎默默插入下划线。

原文:https://learnblockchain.cn/article/11404

Solana基础 - 如何从钱包中获取所有 NFT

import { createUmi } from "@metaplex-foundation/umi-bundle-defaults";
import { publicKey } from "@metaplex-foundation/umi";
import { fetchAllDigitalAssetWithTokenByOwner } from "@metaplex-foundation/mpl-token-metadata";
import { clusterApiUrl } from "@solana/web3.js";

BigInt.prototype.toJSON = function () {
  return this.toString();
};

(async () => {
  try {
    // Create a UMI instance
    const umi = createUmi(clusterApiUrl("devnet"));

    // The owner's public key
    const ownerPublicKey = publicKey(
      "2R4bHmSBHkHAskerTHE6GE1Fxbn31kaD5gHqpsPySVd7",
    );

    console.log("Fetching NFTs...");
    const allNFTs = await fetchAllDigitalAssetWithTokenByOwner(
      umi,
      ownerPublicKey,
    );

    console.log(`Found ${allNFTs.length} NFTs for the owner:`);
    allNFTs.forEach((nft, index) => {
      console.log(`\nNFT #${index + 1}:`);
      console.log("Mint Address:", nft.publicKey);
      console.log("Name:", nft.metadata.name);
      console.log("Symbol:", nft.metadata.symbol);
      console.log("URI:", nft.metadata.uri);
    });

    // If you need the full NFT data
    console.log("\nFull NFT data:");
    console.log(JSON.stringify(allNFTs, null, 2));
  } catch (error) {
    console.error("Error:", error);
  }
})();

https://solana.com/zh/developers/cookbook/tokens/fetch-all-nfts