以太坊开发人员的Solana开发完整指南

在本文中,我们深入探讨了在以太坊和Solana上开发之间的关键区别,指导您如何在Solana上构建。来自以太坊,Solana的外观和感觉将大不相同,并且在您开发时可以使用多样化的工具集。本文将为您提供从以太坊背景构建Solana所需的所有工具。Solana与以太坊有什么不同?帐户模型在Sola

在本文中,我们深入探讨了在以太坊和Solana上开发之间的关键区别,指导您如何在Solana上构建。来自以太坊,Solana的外观和感觉将大不相同,并且在您开发时可以使用多样化的工具集。本文将为您提供从以太坊背景构建Solana所需的所有工具。

Solana与以太坊有什么不同?

帐户模型

在Solana上开发时,最显著的区别是帐户模型设计。理解为什么Solana的帐户模型设计不同是有帮助的。与以太坊不同,Solana旨在利用高端机器中的多个核心。计算资源存在一种趋势,即可用内核的数量随着时间的推移而增加,人们购买的成本越来越便宜。考虑到这一点,帐户模型旨在利用多个核心,创建一个相互并行化交易的系统。这种并行化创造了进一步的优化,例如本地收费市场和更快的吞吐量,我们稍后将探索。

那么,“帐户模型”是什么意思呢?在Solana上,帐户就像包含一些任意数据和特定修改规则的对象。一切都是Solana的帐户,包括智能合约。与以太坊一样,每个帐户都有一个地址标识符来帮助定位帐户。然而,与以太坊不同,每个智能合约都是一个将执行逻辑和存储绑定在一起的帐户,Solana的智能合约是完全无状态的。

Solana的智能合同没有自己的状态,必须将状态传递给他们才能执行。为了说明这一点,让我们看看两个计数器的智能合约,一个在以太坊的Solidity中,一个在Solana上使用Rust。

Ethereum Counter Smart Contract

int private count = 0;
function incrementCounter() public
{ count += 1;
}
function getCount() public constant returns (int) {
return count;
}
}

Solana Counter Program

pub mod counter_anchor {
use super::*;

pub fn initialize_counter(_ctx: Context<InitializeCounter>) -> Result<()> {
Ok(())
}

pub fn increment(ctx: Context<Increment>) -> Result<()> {
ctx.accounts.counter.count = ctx.accounts.counter.count.checked_add(1).unwrap();
Ok(())
}
}

#[derive(Accounts)]
pub struct InitializeCounter<'info> {
#[account(mut)]
pub payer: Signer<'info>,
#[account(
init,
space = 8 + Counter::INIT_SPACE,
payer = payer
)]
pub counter: Account<'info, Counter>,
pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct Increment<'info> {
#[account(mut)]
pub counter: Account<'info, Counter>,
}

#[account]
#[derive(InitSpace)]
pub struct Counter {
count: u64,
}

在Solidity中,您有int private count = 0;,您在Rust智能合约中有一个声明initialize_counter的结构。这个初始计数器创建一个计数为0的帐户,然后您可以将此帐户递增以添加到计数中。这不是在智能合约本身中拥有状态。

有单独的帐户将数据存储在程序之外。要在程序中执行逻辑,您需要传递您想要执行的帐户。在此计数器程序的情况下,您在调用增量函数时将计数器帐户传递给程序,该程序将增加计数器帐户中的值。

Solana帐户模型有什么好处?

Solana帐户模型最重要的好处之一是程序可重用性。

以ERC20为例。ERC20在以太坊上定义了令牌的接口规范。每当有人想制作一个新的令牌时,开发人员都必须使用其指定值将ERC20智能合约重新部署到以太坊上,这会产生重新部署的高成本。

Solana是不同的。在创建新令牌时,您不必将另一个智能合约重新部署到区块链上。相反,您在Solana令牌计划中创建一个新帐户,称为薄荷帐户,该帐户定义了一组值,以给出流通中的令牌数量、小数点、谁可以铸造更多令牌以及谁可以冻结令牌。

Screenshot 2024-06-19 at 7.48.24 AM.png

您无需编写任何Rust或智能合约即可在Solana上部署新令牌。将交易发送到令牌程序,以您选择的语言创建新令牌,然后该令牌将出现在您的钱包中。使用Solana程序库CLI,您可以在单个命令中执行此操作:

spl-token create-token

本地收费市场

拥有索拉纳账户模型的另一个幸运副作用是能够根据国家竞争对费用进行建模。如前所述,交易可以并行执行。然而,它们仅根据写入的帐户并行执行。例如,假设索拉纳上有一个受欢迎的NFT薄荷糖。通常,这种受欢迎程度会提高使用该连锁店的每个人的价格,但相反,不参加NFT铸币厂的每个人都不受影响。

顾名思义,每个账户的收费市场是本地的。如果您将USDC的转账发送给某人,而其他人正在铸造最热门的新NFT,您将不受影响,并继续支付您在Solana上习惯的低费用。这适用于Solana中的任何应用程序,避免了您在以太坊上习惯的常见全球费用市场,同时降低每个人的成本。

Solana的费用是如何运作的?

Solana的费用分为几类:基本费用、优先费用和租金。

基本费用可以根据交易中的签名数量来计算。每个签名需要5000个lamports(0.000000001 sol = 1 lamport)。如果您的交易需要5个签名,基本费用为25000个lamports。这种基本费用为集群的签名验证增加了经济压力,这是计算更密集的行动之一。一半的基本费用被烧毁,一半被奖励给验证者。

优先费用是一种可选费用,任何人都可以将其添加到交易中,以优先于同时执行的其他交易。优先费用是根据交易中使用的计算单位数量来衡量的。计算单位类似于以太坊上的天然气,这是对交易所需的计算资源的简单测量。与以太坊一样,您交易的优先级是根据计算单价和所用计算单位的乘积计算的,或优先级费用=计算单位*计算单价。与基本费用一样,优先费用的一半被烧掉,一半被奖励给验证者。

最终费用,租金,与其说是费用,不如说是押金。当您在网络上创建帐户或分配空间时,您必须为网络存入一些SOL以保留您的帐户。租金是根据存储在网络上的字节数计算的,分配空间需要支付额外的基本费用。重要的是要注意,租金费用不会丢失;如果您关闭帐户并允许集群收回分配的空间,则可以收取租金费用。

Solana上的交易是如何运作的?

在执行交易时支付每笔费用后,了解交易如何运作是件好事。一笔交易由四部分组成:

Solana上的交易是如何运作的?

在执行交易时支付每笔费用后,了解交易如何运作是件好事。一笔交易由四部分组成:

  • 一个或多个说明
  • 要读取或写入的帐户数组
  • 一个或多个签名
  • 最近的blockhash或nonce

指令是Solana上最小的执行逻辑。指令是更新全球索拉纳状态的调用。指令调用调用Solana运行时以更新状态的程序(例如,调用令牌程序将令牌从您的帐户传输到另一个帐户)。你可以认为一个指令就像以太坊智能合约上的函数调用。

以太坊和索拉纳之间的一个显著区别是,由于指令的数量,单个事务中的函数调用数量。每笔交易有多个指令有利于开发人员,因为他们不必创建自定义智能合约来在单笔交易中链接功能。每个指令可以是一个单独的函数调用,在事务中按顺序完成。交易是原子的,这意味着如果这些指令中的任何一个失败,整个交易都将失败,您只需支付交易费用。这就像由于没有在以太坊上设置正确的滑点而导致交易失败

需要记住的另一个关键区别是使用最近的blockhash而不是增量nonce进行交易。当钱包想要进行交易时,将从集群中提取最近的blockhash,以创建有效的交易。在检索最近的blockhash后,此最近的blockhash仅使交易对150个区块有效。这可以防止长寿命的交易签名在更晚的日期执行。

Solana交易的局限性是什么?

与以太坊天然气限制一样,Solana的交易也有特定的计算单位限制。每个限制都可以在下面找到:

Screenshot 2024-06-19 at 8.00.32 AM.png

Solana对交易设置了一些额外的上限。每个被引用的帐户每个块最多可以使用1200万个计算单位。此上限可防止人们在单个区块中多次写入锁定单个帐户,进一步防止当地费用市场被一个帐户覆盖。

事务的另一个限制是您可以在单个指令中进行指令调用的深度。此限制目前设置为4,这意味着在交易恢复之前,您只能在4的深度调用指令。与您在以太坊上必须担心的事情相比,这使得Solana上不存在重新进入的问题。

Mempool在哪里?

与以太坊不同,Mempools在Solana上不存在。Solana验证器在领导者时间表上将交易转发给以下四位领导者。虽然Solana没有mempool,但它仍然有优先费用来帮助订单交易。没有mempool迫使交易从一个领导者跳到另一个领导者,直到blockhash到期,但它减少了跨集群向mempool进行八卦沟通的开销。

我在哪里可以找到智能合同代码?

在EVM世界中,大多数人在查看智能合约地址时熟悉在Etherscan上查找智能合约代码。然而,在索拉纳生态系统的资源管理器上查看智能合约代码相对较新,与EVM标准相比,需要建立。在撰写本文时,Solana.fm是唯一支持根据可验证的构建查看智能合约代码的浏览器。

您可以通过访问浏览器的智能合约地址来找到智能合约代码。例如,转到Pheonix智能合约,您可以在验证选项卡下找到智能合约的代码。从这里,您可以分析代码,并了解智能合约是否是您想要交互的东西。

开发人员环境有什么不同?

编程语言

EVM主要使用Solidity来编写智能合同,而Solana使用Rust。有一个名为Anchor框架的框架,允许您使用您从EVM熟悉的许多工具在Rust中构建,但它仍然是Rust。如果您想在Solana上构建时坚持使用Solidity,一个名为Neon的项目可以使用Solidity。Neon附带了许多您熟悉的工具,例如在开发过程中使用Foundry或Hardhat。使用Neon可能会让您更快地启动和运行,建立在Solana的基础上,但您需要在Neon生态系统之外与其他Solana项目进行更多的可组合性。

像以太坊一样,在客户端,您可以在Solana上找到所有您最喜欢的编程语言的可比SDK。

Screenshot 2024-06-19 at 8.16.31 AM.png

我熟悉的EVM工具在哪里?

当您从EVM迁移到Solana上构建时,您可能正在寻找您熟悉的工具。目前,Solana生态系统没有与Foundry相等的工具,但有相当数量的其他等同于你习惯的工具。

Screenshot 2024-06-19 at 8.17.26 AM.png

智能合约开发有什么不同?

当您在Solana上构建程序或迁移以太坊智能合约时,需要注意几件事。

例如,如果您正在寻找像您习惯于在以太坊智能合约上使用的映射,则这种类型在Solana上并不直接存在。相反,您使用程序衍生地址,简称PDA。与映射一样,程序衍生地址可以使您能够从密钥或帐户创建映射到链上存储的值。您绘制地图的方式与以太坊不同。

假设您想将用户帐户映射到他们在链上的余额。在Solidity中,您会做以下事情:

mapping(address => uint) public balances;

对于程序派生地址,您必须执行以下操作 客户端:

const [BALANCE_PDA] = await anchor.web3.PublicKey.findProgramAddress(
[Buffer.from("BALANCE"), pg.wallet.publicKey.toBuffer()],
pg.program.programId
);

程序:

#[derive(Accounts)]
#[instruction(restaurant: String)]
pub struct BalanceAccounts<'info> {
#[account(
init_if_needed,
payer = signer,
space = 500,
seeds = [balance.as_bytes().as_ref(), signer.key().as_ref()],
bump
)]
pub balance: Account<'info,BalanceAccount>,
#[account(mut)]
pub signer: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[account]
pub struct BalanceAccount {
pub balance: u8
}

地图的密钥来自“平衡”字符串和签名人的公钥的组合,而程序派生地址提供了查找地图值的位置。程序派生地址的功能不仅仅是提供地图;我们可以稍后了解这一点。

在Solidity中,使用代理合同升级智能合同的能力已成为常态。在Solana上,程序是默认可升级的,不涉及任何特殊工作。每个智能合约都可以通过CLI命令solana程序部署<program_filepath进行升级。虽然程序是默认可升级的,但您仍然可以使用solana程序set-upgrade-authority <program_address> --final将其状态降级为不可变。一旦不可变,该程序将在资源管理器上被标记为不可升级。

在编写可靠的智能合同时,您通常会做一件事是检查msg.sender或tx.origin。Solana上没有等价物,因为每笔交易都可以有多个签名人。此外,发送交易的人不一定是签署交易的人,因为你让其他人为你的交易付款。

让我们看看这个基本的索拉纳计划:

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

pub fn initialize(ctx: Context&lt;Initialize>) -> Result&lt;()> {
let the_signer: &mut Signer = &mut ctx.accounts.the_signer;

msg!("The signer: {:?}", *the_signer.key);

Ok(())
}
}

#[derive(Accounts)]
pub struct Initialize&lt;'info> {
#[account(mut)]
pub the_signer: Signer&lt;'info>,
}

这将输出交易的签名人,作为您程序日志的一部分。如前所述,您可以有多个签名人:

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

pub fn initialize(ctx: Context&lt;Initialize>) -> Result&lt;()> {
let the_signer: &mut Signer = &mut ctx.accounts.first_signer;

msg!("The signer: {:?}", *the_signer.key);

Ok(())
}
}

#[derive(Accounts)]
pub struct Initialize&lt;'info> {
#[account(mut)]
pub first_signer: Signer&lt;'info>,
pub second_signer: Signer&lt;'info>,
}

上面的例子表明,这个特定程序有多个签名者,first_signer和second_signer。我们不一定知道哪一个是付款人,但我们知道双方都签署了交易。您可以了解有关在Rareskills上获得签名者的更多信息。

如何在Solana上构建我的EVM项目?

让我们用Solidity构建一个简单的项目,在Solana上完成构建同一项目的过程。你遇到的第一个常见项目是投票项目。Solidity智能合约看起来像这样:

pragma solidity ^0.6.4;

contract Voting {
mapping (bytes32 => uint256) public votesReceived;
bytes32[] public candidateList;

constructor(bytes32[] memory candidateNames) public {
candidateList = candidateNames;
}

function voteForCandidate(bytes32 candidate) public {
require(validCandidate(candidate));
votesReceived[candidate] += 1;
}

function totalVotesFor(bytes32 candidate) view public returns (uint256) {
require(validCandidate(candidate));
return votesReceived[candidate];
}

function validCandidate(bytes32 candidate) view public returns (bool) {
for(uint i = 0; i &lt; candidateList.length; i++) {
if (candidateList[i] == candidate) {
return true;
}
}
return false;
}
}

我们很快注意到一些在Solana程序中不可用的东西。视图功能和映射需要以不同的方式进行。让我们开始在Solana上构建这个程序吧!

让我们创建非常基本的Solana程序外壳:

use anchor_lang::prelude::*;

declare_id!("6voY4gV7kzuGr4hE2xjZnkdagFGNhEe8WonZ8UtdPWig");

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

pub fn init_candidate(ctx: Context&lt;InitializeCandidate>) -> Result&lt;()> {
Ok(())
}

pub fn vote_for_candidate(ctx: Context&lt;VoteCandidate>) -> Result&lt;()> {
Ok(())
}
}

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

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

我们添加了一个访问控制功能检查,该检查将检查init_candidate的签名人是否与智能合同中列出的地址匹配。如果签名者不匹配,将抛出OnlyOwnerError,交易将失败。

让我们进入Solidity智能合同、候选名单和已收到的选票的下一点。虽然您可以在类似于bytes32[]的Solana程序中使用Vec,但管理更改大小的付款可能有点麻烦。相反,我们将使用给定特定候选人名称的程序派生地址,该地址的值是候选人收到的选票。

要在索拉纳计划中使用程序派生帐户,您要在帐户中使用种子和凸起。首先,让我们创建帐户来跟踪已收到的投票。

#[account]
#[derive(InitSpace)]
pub struct Candidate {
pub votes_received: u8,
}

[account]将结构表示为Solana帐户,而#[derive(InitSpace)]是自动计算候选人分配所需空间的有用宏。收到的票可以进行计数,就像Solidity智能合约中收到的票一样。

扩展InitializeCandidate和VoteCandidate,我们得到了以下内容:

#[derive(Accounts)]
#[instruction(_candidate_Name: String)]
pub struct InitializeCandidate&lt;'info> {
#[account(mut)]
pub payer: Signer&lt;'info>,

#[account(
init,
space = 8 + Candidate::INIT_SPACE,
payer = payer,
seeds = [_candidate_Name.as_bytes().as_ref()],
bump,
)]
pub candidate: Account&lt;'info, Candidate>,
pub system_program: Program&lt;'info, System>,
}

#[derive(Accounts)]
#[instruction(_candidate_Name: String)]
pub struct VoteCandidate&lt;'info> {

#[account(
mut,
seeds = [_candidate_Name.as_bytes().as_ref()],
bump,
)]
pub candidate: Account&lt;'info, Candidate>,
}

哇,账户里有很多新代码。让我们打开它。 首先,您会注意到#[instruction(_candidate_Name: String)]。这意味着InitializeCandidate的上下文希望将字符串_candidate_name传递到指令中。我们稍后可以看到这在seeds = [_candidate_name.as_bytes().as_ref()]中使用。这意味着PDA的种子将是_candidate_Name,存储在PDA中的值将是候选人的投票_received。

接下来,您可能对space = 8 + Candidate::INIT_SPACE有一些问题。Candidate::INIT_SPACE是候选人帐户的大小+ 8,8是用于安全检查的Anchor框架帐户开头添加的字节。pub system_program:Program<'info, System>,在创建帐户时需要,用init表示。这意味着,无论何时调用使用InitializeCandidate上下文的指令,该指令都会尝试创建一个候选帐户。

现在,让我们从Solidity智能合约中添加在voteForCandidate中找到的业务逻辑。

pub fn vote_for_candidate(ctx: Context&lt;VoteCandidate>, _candidate_name: String) -> Result&lt;()> {
ctx.accounts.candidate.votes_received += 1;
Ok(())
}

在这里,我们采用前面讨论的附加参数,_candidate_name。这将有助于匹配我们为候选人引用的确切帐户。然后,我们将该候选人的票数增加1。

这就是我们在索拉纳计划方面需要完成的一切,最终的索拉纳计划看起来像这样:


declare_id!("6voY4gV7kzuGr4hE2xjZnkdagFGNhEe8WonZ8UtdPWig");

const OWNER: &str = "8os8PKYmeVjU1mmwHZZNTEv5hpBXi5VvEKGzykduZAik";

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

#[access_control(check(&ctx))]
pub fn init_candidate(ctx: Context&lt;InitializeCandidate>, _candidate_name: String) -> Result&lt;()> {
Ok(())
}

pub fn vote_for_candidate(ctx: Context&lt;VoteCandidate>, _candidate_name: String) -> Result&lt;()> {
ctx.accounts.candidate.votes_received += 1;
Ok(())
}
}

#[derive(Accounts)]
#[instruction(_candidate_name: String)]
pub struct InitializeCandidate&lt;'info> {
#[account(mut)]
pub payer: Signer&lt;'info>,

#[account(
init,
space = 8 + Candidate::INIT_SPACE,
payer = payer,
seeds = [_candidate_name.as_bytes().as_ref()],
bump,
)]
pub candidate: Account&lt;'info, Candidate>,
pub system_program: Program&lt;'info, System>,
}

#[derive(Accounts)]
#[instruction(_candidate_name: String)]
pub struct VoteCandidate&lt;'info> {
#[account(
mut,
seeds = [_candidate_name.as_bytes().as_ref()],
bump,
)]
pub candidate: Account&lt;'info, Candidate>,
}

#[account]
#[derive(InitSpace)]
pub struct Candidate {
pub votes_received: u8,
}

fn check(ctx: &Context&lt;InitializeCandidate>) -> Result&lt;()> {
// Check if signer === owner
require_keys_eq!(
ctx.accounts.payer.key(),
OWNER.parse::&lt;Pubkey>().unwrap(),
OnlyOwnerError::NotOwner
);

Ok(())
}

#[error_code]
pub enum OnlyOwnerError {
#[msg("Only owner can call this function!")]
NotOwner,
}

现在你可能会想,“但是等等,Solidity智能合约中的totalVotesFor和有效候选人怎么样?”validCandidate已经入账,因为如果您通过一个不存在的帐户,vote_for_candidate将失败。totalVotesFor可以使用Typescript在客户端完成,并且不需要存在于Solana程序中。

现在我们已经构建了Solana程序,让我们与它进行互动。

将程序加载到Solana Playground中,我可以将其构建并部署到Devnet。一旦您构建并部署该程序,您会发现您可以使用测试选项卡上的说明运行测试。

这类似于使用Remix来测试您的Solidity智能合约。打开initCandidate并输入John Smith作为候选名称,我们现在必须为John Smith生成PDA。单击候选人帐户查找器,然后选择From seed。选择自定义字符串并输入John Smith,最后单击生成。恭喜你,你刚刚为约翰·史密斯找到了你的PDA!现在点击测试来执行指令。

如果全部成功,您应该会在测试事务上看到以下程序日志。 Screenshot 2024-06-19 at 8.32.12 AM.png 现在让我们投票给约翰·史密斯!打开投票候选人指示,键入约翰·史密斯并再次生成相同的PDA。点击测试来投票给你的第一位候选人! Screenshot 2024-06-19 at 8.32.23 AM.png 既然你已经投票了,你如何检查候选人有多少票?前往测试选项卡上“帐户”下的候选人,然后点击“全部获取”按钮。这将获取所有有效的候选人及其选票。从那里,您将收到一系列候选人、他们的帐户地址和他们的选票。

Screenshot 2024-06-19 at 8.33.02 AM.png

恭喜!您刚刚将投票Solidity智能合约转换为Solana程序。您可以在其他Solidity智能合约上使用许多相同的技术,在Solana上构建您在EVM上拥有的东西。如果您有兴趣了解有关Solana的更多信息,请查看文档并立即开始。

点赞 1
收藏 1
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
blockchain002
blockchain002
江湖只有他的大名,没有他的介绍。