Skip to main content

Solang solidity合约实现 - hello, World

欢迎来到Solana入门指南!Solang是一个Solidity编译器,它允许你使用Solidity编程语言编写Solana程序,其他区块链中称为“智能合约”。

如果你是一位对Solana网络的高速和低费用感兴趣的EVM开发者,那么Solang是你的完美工具。通过Solang,你可以利用你对Solidity的现有知识开始在Solana上进行构建!

安装

在本节中,我们将帮助你设置Solang的开发环境。只需按照下面列出的步骤进行操作即可:

  1. 检查先决条件:在开始之前,请确保你的系统上已安装了RustNode.js。Windows用户还需要设置好Windows子系统以便运行Linux

  2. Solana工具套件安装:首先安装Solana工具套件,其中包括Solana命令行界面(CLI)和最新版本的Solang

  3. Anchor框架安装:接下来,安装Anchor框架AnchorSolana生态系统中广泛使用的框架,可以简化构建Solana程序的过程。从0.28版本开始,你可以直接通过Anchor开始使用Solang进行构建。

截至撰写本文时,请使用以下命令安装Anchor,以确保与Solang版本0.3.1兼容:

cargo install --git https://github.com/coral-xyz/anchor anchor-cli --locked --force
  1. Solang扩展适用于VSCode:如果你是Visual Studio Code(VSCode)的用户,建议安装Solang扩展以辅助语法高亮显示。请记得禁用任何活动的Solidity扩展,以确保Solang扩展正常工作。

创建一个新项目

一旦你安装了Solana CLIAnchor,你可以使用以下命令创建一个新项目:

anchor init project_name --solidity

该命令生成一个新项目,其中包含一个基本的Solang on-chain程序(相当于EVM上的智能合约)和一个测试文件,演示了如何从客户端与该程序进行交互。

链上程序概述

接下来,让我们来看一下从链上程序本身开始的初始代码。在你的项目的 ./solidity 目录中,你将找到下面的合约,其中包括:

  • 一个 constructor 用于初始化状态变量的函数
  • 一个用于将消息打印到程序日志的函数
  • 一个用于更新状态变量的函数
  • 一个函数,用于返回状态变量的当前值
@program_id("F1ipperKF9EfD821ZbbYjS319LXYiBmjhzkkf5a26rC")
contract starter {
bool private value = true;

@payer(payer)
constructor(address payer) {
print("Hello, World!");
}

/// A message that can be called on instantiated contracts.
/// This one flips the value of the stored `bool` from `true`
/// to `false` and vice versa.
function flip() public {
value = !value;
}

/// Simply returns the current value of our `bool`.
function get() public view returns (bool) {
return value;
}
}

重要的差异

EVM智能合约相比,你可能会注意到两个重要的区别:

  1. @program_id 注解: 在Solana上,智能合约被称为“程序”。使用 @program_id 注释来指定程序的链上地址。
@program_id("F1ipperKF9EfD821ZbbYjS319LXYiBmjhzkkf5a26rC") // on-chain program address
  1. @payer 注解:

在链上存储数据时,需要分配一定数量的SOL来支付存储成本。注释 @payer 指定了将支付所需SOL以创建用于存储状态变量的账户的用户。

@payer(payer) // payer for the "data account"
constructor(address payer) {
print("Hello, World!");
}

状态数据的存储

EVM智能合约和Solana程序之间的一个重要区别在于它们如何存储“状态”变量/数据:

  • EVM智能合约可以直接存储状态变量。
  • Solana的链上程序则会创建单独的账户来存储状态数据。这些账户通常被称为“数据账户”,并且是由程序“拥有”。

在这个例子中,当合约部署时,它被部署到 @program_id 中指定的地址。当程序部署后调用 constructor 时,会创建一个独立的帐户,用于存储状态变量,而不是存储在合约本身内部。

这可能听起来有点不同于你所习惯的,但别担心!让我们来看一下测试文件,以更好地理解这个概念。

测试文件概述

起始测试文件可以在 ./tests 目录中找到。该文件提供了一个与客户端交互的示例。

Anchor设置了 providerprogram ,以帮助我们从客户端连接到合约。这是通过使用IDL文件来完成的,该文件描述了程序的公共接口,类似于EVM智能合约中使用的ABI文件。如果你运行 anchor build ,则会生成IDL文件,并且可以在 ./target/idl 找到。

import * as anchor from "@coral-xyz/anchor"
import { Program } from "@coral-xyz/anchor"
import { Starter } from "../target/types/starter"

describe("starter", () => {
// Configure the client to use the local cluster.
const provider = anchor.AnchorProvider.env()
anchor.setProvider(provider)

const dataAccount = anchor.web3.Keypair.generate()
const wallet = provider.wallet

const program = anchor.workspace.Starter as Program<Starter>

it("Is initialized!", async () => {
// Add your test here.
const tx = await program.methods
.new(wallet.publicKey)
.accounts({ dataAccount: dataAccount.publicKey })
.signers([dataAccount])
.rpc()
console.log("Your transaction signature", tx)

const val1 = await program.methods
.get()
.accounts({ dataAccount: dataAccount.publicKey })
.view()

console.log("state", val1)

await program.methods
.flip()
.accounts({ dataAccount: dataAccount.publicKey })
.rpc()

const val2 = await program.methods
.get()
.accounts({ dataAccount: dataAccount.publicKey })
.view()

console.log("state", val2)
})
})

在测试文件中,我们首先生成一个新的密钥对,用于创建存储合约状态的“数据账户”。

const dataAccount = anchor.web3.Keypair.generate();

接下来,我们使用 new 指令来创建一个新的数据账户。这个指令对应于合约的 constructor 。新创建的数据账户将被初始化,用于存储合约中定义的状态变量。

在这里, payer 被指定为 wallet.publicKey ,并提供了我们计划创建的 dataAccount 的地址。生成的 dataAccount Keypair作为交易的附加签名者包含在其中,因为它被用于创建一个新的账户。基本上,这个操作验证了我们持有与我们正在创建的新账户地址相对应的私钥。

// Client
const tx = await program.methods
.new(wallet.publicKey)
.accounts({ dataAccount: dataAccount.publicKey })
.signers([dataAccount])
.rpc()

// on-chain program
@payer(payer)
constructor(address payer) {
print("Hello, World!");
}

合约的 get 函数被调用以获取存储在指定 dataAccount 中的值。

// Client
const val1 = await program.methods
.get()
.accounts({ dataAccount: dataAccount.publicKey })
.view()

// on-chain program
function get() public view returns (bool) {
return value;
}

接下来,合约的 flip 函数被用来修改指定 dataAccount 的状态。

// Client
await program.methods
.flip()
.accounts({ dataAccount: dataAccount.publicKey })
.rpc()

// on-chain program
function flip() public {
value = !value;
}

要运行测试,请在终端中使用 anchor test 命令。

anchor test 命令执行以下任务:

  • 启动本地Solana验证节点
  • 构建并部署你的链上程序到本地验证节点
  • 运行测试文件

接下来应该在控制台中显示以下输出:

Your transaction signature 2x7jh3yka9LU6ZeJLUZNNDJSzq6vdUAXk3mUKuP1MYwr6ArYMHDGw6i15jJnMtnC7BP7zKactStHhTekjq2vh6hP
state true
state false
✔ Is initialized! (782ms)

你可以在 ./.anchor/program-logs 中查看程序日志,那里会找到“Hello, World!”的消息

Program F1ipperKF9EfD821ZbbYjS319LXYiBmjhzkkf5a26rC invoke [1]
Program 11111111111111111111111111111111 invoke [2]
Program 11111111111111111111111111111111 success
Program log: Hello, World!

恭喜!你成功地使用 Solang 构建了你的第一个 Solana 程序!虽然与标准 Solidity 智能合约相比可能存在一些差异,但 Solang 提供了一个极好的桥梁,帮助你利用现有的 Solidity 技能和经验来构建 Solana 上的应用。

下一步

有兴趣深入了解吗?请查看 solana-developers/program-examples 存储库。你将在 basicstokens 部分找到适用于常见Solana用例的Solang实现。

如果你有问题,请随时在Solana Stack exchange上发布。如果你有关于Solang维护者的问题,可以直接在Hyperledger Foundationdiscord上联系他们。

玩得开心,尽情建造吧!