Solana vs. Solidity:编程模型

Solana是快速崛起的公链,但是其开发文档令人生畏。本文以简明易懂的方式介绍Solana编程模型与以太坊Solidity的差异。

Solana vs. Solidity:编程模型
一键发币: SOL | BNB | ETH | BASE | Blast | ARB | OP | POLYGON | AVAX | FTM | OK

如果你熟悉以太坊和 Solidity,可能会想尝试一下 Solana 生态系统。 Solana的快速区块链令人鼓舞和令人兴奋。 另外,还可以增加 web3 知识的覆盖范围。

但如果你从未见过 Solana 程序,它的语法会非常可怕,而且文档看起来像是一个开发人员重新调整用途的笔记。 没有连贯性,直到以后的部分才能理解某些部分。

这就是为什么我创建了“Solana Ramp for Solidity Devs”系列,以温和、无痛苦和沮丧的方式向你介绍 Solana。 本系列的第一篇文章(本文)是“以太坊与 Solana 的编程模型比较”,本质上只是在 ETH 与 SOL 中编写程序的方式上的概念差异。

我们不会深入探讨 Solana 的幕后工作方式,例如历史证明(PoH:Proof of History)等。这将在另一篇文章中讨论。

1、以太坊与 Solana 中存储状态

在以太坊中,你习惯于将状态存储在智能合约本身中。 看看这个简单的合约:

contract EthereumExample {
  struct Author {
    uint256 publications;
    uint256 likes;
  }
  
  Author public author;
  
  function publish() public {
      author.publications += 1;
  }
}

它只存储一个变量author,并有一个 publish() 函数来改变author变量。 你部署此合约,并将代码及其状态存储在一个地址中。 可以使用该地址来引用合约并读取其数据(例如在 Etherscan 上)。

但Solana不同。

Solana 合约是无状态的,应该将它们视为只是指令,合约不存储任何数据/状态。 那么数据存储在哪里呢? 它存储在单独的“帐户”中。 帐户保存数据。 当你调用 Solana 合约的函数时,需要将数据传递给该函数。

因此上面的 publish() 函数需要引用存储数据的帐户。 该函数将增加 publiction的数量,但 author 变量仍将存储在同一帐户中(而不是在合约中)。 合约的状态没有改变。 如果你来自 Java 世界,Solana 合约就像 Java 中的静态类。

顺便说一句,智能合约在 Solana 中被称为 Program (程序)。

以下是上面的简单合约在 Solana 中的样子:

#[program]
pub mod solana_example {
    use super::*;
    pub fn publish(ctx: Context<AuthorData>) -> ProgramResult {
        let author_account = &mut ctx.accounts.author_account;
        author_account.publications += 1;
        Ok(())
    }
}

#[derive(Accounts)]
pub struct AuthorData<'info> {
    #[account(mut)]
    pub author_account: Account<'info, AuthorAccount>,
}

#[account]
pub struct AuthorAccount {
    pub publications: u64,
    pub likes: u64,
}

请注意数据如何存储在单独的 AuthorAccount 中,并且该数据是通过 Context引用传入 publish() 函数的。 solana_example 程序本身不存储任何内容。 它仅对传入的数据进行操作。 AuthorData 有点像将 AuthorAccount 传递给发布函数所需的包装器。

我知道,语法非常可怕。 这是 Rust 和 Anchor(Solana 框架)。 这让我想起了我开始 iOS 开发并第一次接触 Objective C 的日子。 别担心,我们将在本系列的第二篇文章中更详细地了解语法。 我保证,这会更有意义。

将代码与数据分离使得程序升级变得容易。 在 Solana 中,可以将程序的新版本重新部署到同一地址,同时重复使用同一数据帐户 — 升级不会丢失数据,而这一点在以太坊中很难做到。

2、Solana 账户

帐户(account)是一个含糊不清的词。 它在不同的背景下意味着很多东西。 在 Solana 中,帐户仅意味着存储单元。 它只是一个存储任意数据的容器。

有 2 种类型的帐户。 第一个是数据帐户(data account),它只存储我们已经提到的类似 AuthorAccount 等程序数据。 第二种是程序账户(program account),用于“托管”程序代码。 当你在 Solana 上部署程序时,其代码存储在“程序帐户”中。

举个例子:如果你有一个计数器程序可以让你递增计数器,则必须创建两个帐户:一个帐户用于存储程序代码,另一个帐户用于存储计数器值。

帐户拥有公钥/地址,以便能够引用它们,并且拥有私钥,用于签名以证明修改帐户的权限

权威(authority)这个词在 Solana 世界中被频繁使用。 它仅仅意味着所有者—私钥的持有者。

帐户还存储余额,Solana余额。 Solana 的原生货币单位是 SOL 和 Lamport(以纪念 Solana 最大的技术影响力 Leslie Lamport 命名)。 1 SOL = 10⁹  Lamport。 这些是 ETH 和 wei 的类似物。

为了创建帐户,Solana 需要在其存储上分配空间。 Solana 上的存储不是免费的,因此创建帐户也不是免费的。 需要向 Solana 支付租金来“托管”你的帐户。 但别担心。 如果你将 2 年的租金存入你的帐户,将免收租金。 每个人都这样做,所以 Solana 上的存储基本上是免费的。

以下是 Solana 帐户中存储的所有内容的摘要:

来源:Solana Wiki

数据字段存储代码或任意数据,具体取决于帐户是否可执行( executable)。 我们稍后会讨论一下 owner 字段。

以太坊也有两种类型的账户:

  • 外部拥有账户(externally owned account)— 可以由你的钱包生成的常规帐户(只需生成私钥,然后导出公钥和地址,你就拥有了一个帐户)。 这些帐户仅存储余额和随机数。
  • 智能合约账户(smart contract account)—这些账户存储 EVM 代码,并且还有一个可用于存储任意数据的存储映射。

以下是以太坊账户中存储的内容,以供比较:

来源:Solana Wiki

codeHash用于存储代码, storageRoot用于存储任意数据。 对于不可执行的帐户, storageRoot 设置为特殊的“null”哈希值,表示该帐户没有存储空间。

在以太坊中,只有“可执行帐户”才有存储空间。 但在 Solana 中,所有帐户都可以存储数据。 但是,可执行帐户数据专门用于不可变字节代码。 所有其他数据都存储在可执行帐户拥有的非可执行帐户中。

现在,我们来谈谈 Solana 账户的 owner 字段。

为了确保合约无法修改另一个合约的状态,每个数据帐户都会分配一个所有者程序(owner program),该程序对状态变化(state mutation)具有独占控制权。 默认情况下,所有者程序是 Solana 的系统程序(有点像操作系统)。

除所有者之外,任何人都无法修改数据帐户的状态。 任何人都可以向帐户存钱,但只有所有者可以提取余额。

此时,你的技术程序和帐户就可以在 Solana 上运行了。 但 Solana 有一点以太坊中没有的尴尬之处。

想象一下,你将一个程序部署到 Solana,并且还将传统的 web2 前端部署到 AWS 以与该程序交互。 每次调用程序时,都需要传入数据账户(修改状态)。 你需要拥有数据帐户的私钥才能更改数据帐户的状态。

钥匙的管理由你负责。 你要把它存放在哪里? 在 web2 服务器中作为环境变量? 这不是很web3。 最好将此密钥存储在程序本身中,使其更像以太坊—一种将存储附加到程序的方式。

程序派生地址 (PDA) 解决了这个问题。

3、程序派生地址 (PDA)

PDA (Program Derived Address)本质上允许你将数据存储帐户附加到无状态程序帐户。 一种让 Solana 变得类似于以太坊的方法。

它是如何工作的? 在程序中,你只需从程序控制的变量中生成一个地址。 这成为派生帐户(程序派的生地址)。 Solana OS 提供了一个辅助函数来派生此地址。

更具体地说,PDA 源自程序 ID 和种子集合。 程序 ID 是 Solana 程序的地址。 程序可以任意选择种子(我们将看到种子在哪里有用)。

该过程是确定性的:种子和程序 ID 的组合通过 sha256 哈希函数运行,以查看它们是否生成位于椭圆曲线上的公钥(生成的公钥大约有 50% 位于椭圆曲线上) 椭圆曲线上)。 如果它确实位于椭圆曲线上,我们只需添加一些东西来稍微修改我们的输入,然后重试。 这个模糊因素的技术术语是 bump。 在 Solana 中,我们从 bump = 255 开始,然后简单地向下迭代 bump = 254bump = 253 等,直到我们得到不在椭圆曲线上的地址。

“位于椭圆曲线上”是什么意思? 当公钥位于椭圆曲线上时,意味着存在相应的私钥,该私钥将使整个私钥密码算法起作用。

有一个名为 findProgramDerivedAddress 的函数可以抽象出整个过程。

哦,这里有很多技术性的东西,但本质上 PDA 是从程序 ID 和一些种子生成的,这样结果地址就没有相应的私钥。 那么,PDA 对于将存储附加到 Solana 程序有何用处?

PDA 解决了一些问题:

  • 无需管理/跟踪存储帐户的私钥。 只需从程序中获取一个地址并使用该地址/帐户作为存储即可。 如何确保其他人不会修改派生帐户? 因为派生地址没有对应的私钥。 因此任何人都无法修改此帐户。 Solana OS 确保仅允许程序修改 PDA。 如何确保其他程序无法导出相同的 PDA? 他们不能,因为他们的程序 ID 会不同。
  • PDA 还可用于在单独的帐户中存储用户特定信息。 这就是种子发挥作用的地方。 常见的做法是使用最终用户的公钥作为种子来生成 PDA,从而允许我们的程序将有关该用户的信息存储在其自己的独立帐户中。 程序可以通过使用不同的种子确定地导出任意数量的地址。 这些种子可以象征性地标识地址的使用方式。 例如,你可以使用用户的公钥和代币的符号作为种子,并获取一个用于存储用户有关特定代币的信息的帐户(每个用户和每个代币都会有一个新的 PDA 帐户)。

总而言之,PDA只是一个帐户,其所有者是一个程序,并不像其他帐户那样拥有私钥。 由于没有关联的私钥,外部用户无法为 PDA 生成有效签名。 只有种子生成 PDA 的程序才能控制它 — 这是由 Solana OS 强制执行的。


原文链接:Getting Started With Solana for Solidity Developers

DefiPlot翻译整理,转载请标明出处

通过 NowPayments 打赏