编写智能合约

Icon Link编写 Sway 智能合约

Icon Link安装

要安装 Fuel 工具链,你可以使用 fuelup-init 脚本。 这将安装 forcforc-clientforc-fmtforc-lspforc-wallet 以及 fuel-core~/.fuelup/bin

curl https://install.fuel.network | sh
Icon InfoCircle

遇到问题了吗?访问 安装指南 或在我们的 论坛 Icon Link 发帖提问。

如果您正在使用 VSCode,我们建议安装 Sway 扩展 Icon Link

Icon Link已安装 fuelup

如果您已经安装了 fuelup,请运行以下命令,确保您的工具链是最新的。

fuelup self update
fuelup update
fuelup default latest

Icon Link您的第一个 Sway 项目

我们将构建一个简单的计数器合约,其中包含两个函数:一个用于递增计数器,一个用于返回计数器的值。

首先创建一个新的空文件夹。我们将其命名为 fuel-project

mkdir fuel-project

Icon Link编写合约

进入您的 fuel-project 文件夹:

cd fuel-project

然后使用 forc 创建一个合约项目:

forc new counter-contract

您将看到以下输出:

To compile, use `forc build`, and to run tests use `forc test`
----
Read the Docs:
- Sway Book: https://docs.fueldev.xyz/docs/sway
- Forc Book: https://docs.fueldev.xyz/docs/forc
- Rust SDK Book: https://docs.fueldev.xyz/docs/fuels-rs
- TypeScript SDK: https://docs.fueldev.xyz/docs/fuels-ts

Join the Community:
- Follow us @SwayLang: https://twitter.com/SwayLang
- Ask questions on Discourse: https://forum.fuel.network/

Report Bugs:
- Sway Issues: https://github.com/FuelLabs/sway/issues/new

这是 forc 初始化的项目:

tree counter-contract
counter-contract
├── Forc.toml
└── src
    └── main.sw

1 directory, 2 files

forc.toml清单文件(类似于 Cargo 的 Cargo.toml 或 Node 的 package.json),用于定义项目元数据,例如项目名称和依赖项。

在代码编辑器中打开您的项目,并删除 src/main.sw 中除第一行之外的所有内容。

每个 Sway 文件都必须以声明文件包含的程序类型开始;在这里,我们声明了这个文件是一个合约。您可以在 Sway 书 中了解更多关于 Sway 程序类型的信息。

contract;

接下来,我们将定义一个存储值。 在我们的案例中,我们有一个名为 counter 的单一计数器,类型为 u64(64 位无符号整数),并将其初始化为 0。

storage {
    counter: u64 = 0,
}

Icon LinkABI

ABI 意为应用二进制接口。 ABI 定义了合约的接口。 合约必须定义或导入 ABI 声明。

将 ABI 定义在单独的库中并导入到合约中被认为是最佳实践。 这样做可以让调用合约的人更轻松地导入和使用 ABI。

为简单起见,我们将直接在合约文件中定义 ABI。

abi Counter {
    #[storage(read, write)]
    fn increment();
 
    #[storage(read)]
    fn count() -> u64;
}

Icon Link实现 ABI

在您的 ABI 定义下面,您将编写 ABI 中定义的函数的实现。

impl Counter for Contract {
    #[storage(read)]
    fn count() -> u64 {
        storage.counter.read()
    }
 
    #[storage(read, write)]
    fn increment() {
        let incremented = storage.counter.read() + 1;
        storage.counter.write(incremented);
    }
}
Icon InfoCircle

storage.counter.read() 是一个隐式返回,等同于 return storage.counter.read();

到目前为止,您的代码应该是这样的:

文件:./counter-contract/src/main.sw

contract;
 
storage {
    counter: u64 = 0,
}
 
abi Counter {
    #[storage(read, write)]
    fn increment();
 
    #[storage(read)]
    fn count() -> u64;
}
 
impl Counter for Contract {
    #[storage(read)]
    fn count() -> u64 {
        storage.counter.read()
    }
 
    #[storage(read, write)]
    fn increment() {
        let incremented = storage.counter.read() + 1;
        storage.counter.write(incremented);
    }
}

Icon Link构建合约

导航到您的合约文件夹:

cd counter-contract

然后运行以下命令来构建您的合约:

forc build
  Compiled library "core".
  Compiled library "std".
  Compiled contract "counter-contract".
  Bytecode size: 84 bytes.

让我们在构建后查看 counter-contract 文件夹的内容:

tree .
.
├── Forc.lock
├── Forc.toml
├── out
│   └── debug
│       ├── counter-contract-abi.json
│       ├── counter-contract-storage_slots.json
│       └── counter-contract.bin
└── src
    └── main.sw

3 directories, 6 files

我们现在有一个 out 目录,其中包含我们的构建结果,如我们 ABI 的 JSON 表示和合约字节码。

Icon Link使用 Rust 测试您的合约

Icon InfoCircle

不想使用 Rust 进行测试?跳过此部分,转到 部署合约

我们将从使用 Cargo 生成模板添加一个 Rust 集成测试套件开始。 如果您尚未安装 Rust,可以通过运行以下命令来安装它:

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

接下来,如果您尚未安装它,请安装 cargo generate Icon Link

cargo install cargo-generate --locked

现在,让我们使用以下命令生成默认的测试套件:

cargo generate --init fuellabs/sway templates/sway-test-rs --name counter-contract
⚠️   Favorite `fuellabs/sway` not found in config, using it as a git repository: https://github.com/fuellabs/sway.git
🔧   Destination: /home/user/path/to/counter-contract ...
🔧   project-name: counter-contract ...
🔧   Generating template ...
🔧   Moving generated files into: `/home/user/path/to/counter-contract`...
✨   Done! New project created /home/user/path/to/counter-contract

让我们看一下结果:

tree .
.
├── Cargo.toml
├── Forc.lock
├── Forc.toml
├── out
│   └── debug
│       ├── counter-contract-abi.json
│       ├── counter-contract-storage_slots.json
│       └── counter-contract.bin
├── src
│   └── main.sw
└── tests
    └── harness.rs

4 directories, 8 files

我们有两个新文件!

  • Cargo.toml 是我们新测试套件的清单,指定了所需的依赖项,包括 fuels(Fuel Rust SDK)。
  • tests/harness.rs 包含一些样板测试代码,让我们开始,尽管目前还没有调用任何合约方法。

打开您的 Cargo.toml 文件并检查在 dev-dependencies 下使用的 fuels 的版本。如果尚未安装,请将版本更改为 0.62.0

[dev-dependencies]
fuels = { version = "0.62.0", features = ["fuel-core-lib"] }
tokio = { version = "1.12", features = ["rt", "macros"] }

现在我们有了默认的测试套件,让我们向其中添加一个有用的测试。

test/harness.rs 文件的最底部,下面 can_get_contract_id() 测试之后,添加以下 test_increment 测试函数以验证计数器的值是否得到了增加:

#[tokio::test]
async fn test_increment() {
    let (instance, _id) = get_contract_instance().await;
 
    // Increment the counter
    instance.methods().increment().call().await.unwrap();
 
    // Get the current value of the counter
    let result = instance.methods().count().call().await.unwrap();
 
    // Check that the current value of the counter is 1.
    // Recall that the initial value of the counter was 0.
    assert_eq!(result.value, 1);
}

以下是您的文件应该是什么样子: 文件:./counter-contract/tests/harness.rs

use fuels::{prelude::*, types::ContractId};
 
// Load abi from json
abigen!(Contract(
    name = "MyContract",
    abi = "out/debug/counter-contract-abi.json"
));
 
async fn get_contract_instance() -> (MyContract<WalletUnlocked>, ContractId) {
    // Launch a local network and deploy the contract
    let mut wallets = launch_custom_provider_and_get_wallets(
        WalletsConfig::new(
            Some(1),             /* Single wallet */
            Some(1),             /* Single coin (UTXO) */
            Some(1_000_000_000), /* Amount per coin */
        ),
        None,
        None,
    )
    .await
    .unwrap();
    let wallet = wallets.pop().unwrap();
 
    let id = Contract::load_from(
        "./out/debug/counter-contract.bin",
        LoadConfiguration::default(),
    )
    .unwrap()
    .deploy(&wallet, TxPolicies::default())
    .await
    .unwrap();
 
    let instance = MyContract::new(id.clone(), wallet);
 
    (instance, id.into())
}
 
#[tokio::test]
async fn can_get_contract_id() {
    let (_instance, _id) = get_contract_instance().await;
 
    // Now you have an instance of your contract you can use to test each function
}
 
#[tokio::test]
async fn test_increment() {
    let (instance, _id) = get_contract_instance().await;
 
    // Increment the counter
    instance.methods().increment().call().await.unwrap();
 
    // Get the current value of the counter
    let result = instance.methods().count().call().await.unwrap();
 
    // Check that the current value of the counter is 1.
    // Recall that the initial value of the counter was 0.
    assert_eq!(result.value, 1);
}

在终端中运行 cargo test

cargo test

如果一切顺利,输出应如下所示:

  ...
  running 2 tests
  test can_get_contract_id ... ok
  test test_increment ... ok
  test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.25s

Icon Link部署合约

现在是时候部署了。我们将展示如何使用命令行中的 forc 进行部署,但您也可以使用 Rust SDK TypeScript SDK 进行部署。

为了部署合约,您需要拥有一个钱包来签署交易并支付 Gas 费用。Fuelup 将指导您完成此过程。

Icon Link设置本地钱包

forc-wallet 插件随默认分发的工具链一起打包安装,因此如果你按照上述说明操作,应该已经安装了它。

要使用 forc-wallet 初始化一个新钱包,可以运行以下命令:

forc wallet new

输入密码后,务必保存输出的助记词。

接下来,创建一个新的钱包账户:

forc wallet account new

这样,你将获得一个类似于这样的 Fuel 地址:fuel1efz7lf36w9da9jekqzyuzqsfrqrlzwtt3j3clvemm6eru8fe9nvqj5kar8

如果你需要列出你的账户,可以运行以下命令:

forc wallet accounts

您可以使用 faucet Icon Link 获取测试资金。

Icon Link部署到测试网

现在,您可以使用 forc deploy 命令将合约部署到最新的测试网。

forc deploy --testnet

终端将要求您输入钱包的密码:

Please provide the password of your encrypted wallet vault at "~/.fuel/wallets/.wallet":

解锁钱包后,终端将显示账户列表:

Account 0 -- fuel18caanqmumttfnm8qp0eq7u9yluydxtqmzuaqtzdjlsww5t2jmg9skutn8n:
  Asset ID                                                           Amount
  0000000000000000000000000000000000000000000000000000000000000000 499999940

在列表下方,您将看到此提示:

Please provide the index of account to use for signing:

然后,您将输入首选账户的编号,并在提示时按 Y 以接受交易。

最后,您将获得部署合约的网络端点、Contract ID 和交易签名的区块。

保存 Contract ID,因为您稍后需要用到它来连接前端。

Contract counter-contract Deployed!

Network: https://testnet.fuel.network
Contract ID: 0x8342d413de2a678245d9ee39f020795800c7e6a4ac5ff7daae275f533dc05e08
Deployed in block 0x4ea52b6652836c499e44b7e42f7c22d1ed1f03cf90a1d94cd0113b9023dfa636

Icon Link恭喜,您已经完成了在 Fuel 上的第一个智能合约部署 ⛽

这是该项目的仓库 Icon Link。如果您遇到任何问题,一个很好的第一步是将您的代码与该仓库进行比较,并解决任何差异。

在 Twitter 上给我们发推 @fuel_network Icon Link,让我们知道您刚在 Fuel 上构建了一个 dapp,您可能会被邀请加入建设者的私人群组,被邀请参加下一次 Fuel 晚宴,获得项目的 Alpha 版本,或者其他什么事情 👀。

Icon Link需要帮助吗?

通过在 Fuel 论坛 Icon Link 上发布您的问题来获取团队的帮助。