4 个版本 (2 个重大变更)

0.3.1 2024年6月18日
0.3.0 2024年4月30日
0.2.0 2024年1月18日
0.1.0 2024年1月10日

#23#signer

Download history 239/week @ 2024-05-04 363/week @ 2024-05-11 189/week @ 2024-05-18 279/week @ 2024-05-25 190/week @ 2024-06-01 127/week @ 2024-06-08 391/week @ 2024-06-15 154/week @ 2024-06-22 164/week @ 2024-06-29 118/week @ 2024-07-06 209/week @ 2024-07-13 299/week @ 2024-07-20 246/week @ 2024-07-27 229/week @ 2024-08-03 198/week @ 2024-08-10 76/week @ 2024-08-17

773 每月下载量

MIT 许可证

165KB
3K SLoC

入门指南

创建新的 Rust 应用

第一步是创建一个二进制 rust 项目。

cargo new zilliqa-rs-demo

然后我们需要将 zilliqa-rs 和 tokio 添加到项目的依赖中

cargo add zilliqa-rs tokio

调用简单的 JSON-RPC API

使用 Docker 运行 isolated-server

这里我们使用 Docker 运行一个 isolated server,将其用作目标网络,但您可以使用任何您想要的 Zilliqa 网络。

docker run -d -p 5555:5555 --name iso-server zilliqa-isolated-server:latest

调用 GetBalance

首先,我们需要创建一个 provider。在 main 的第一行,我们创建了一个 HTTP provider。我们使用上一行运行的 isolated server 的 URL。这个网络的链 ID 是 222。然后我们可以调用 provider 的 get_balance 函数,传入我们想要获取余额的账户地址。

use std::error::Error;

use zilliqa_rs::middlewares::Middleware;
use zilliqa_rs::providers::{Http, Provider};

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    let provider = Provider::<Http>::try_from("http://127.0.0.1:5555")?.with_chain_id(222);
    let balance = provider
        .get_balance("0x381f4008505e940ad7681ec3468a719060caf796")
        .await;

    println!("{balance:?}");
    Ok(())
}

发送交易

带有 signers 的 provider

要开始发送交易,我们需要更改 provider。我们之前拥有的 provider 没有signer,那是因为我们不打算发送交易。但现在我们想要发送,所以我们需要为它提供一个 signers。

    let wallet = "0xe53d1c3edaffc7a7bab5418eb836cf75819a82872b4a1a0f1c7fcf5c3e020b89"
        .parse::<LocalWallet>()?;

    let provider = Provider::<Http>::try_from("http://127.0.0.1:5555")?
        .with_chain_id(222)
        .with_signer(wallet.clone());

这里,我们从一个私钥和一个带有该 signers 的 provider 创建一个新的钱包。这个 provider 现在可以用来发送交易。

让我们将一些 ZIL 转移到随机地址。首先,我们创建一个随机钱包

    let receiver = LocalWallet::create_random()?;

然后我们需要构建一个交易。 TransactionBuilder 用于构建交易

    let tx = TransactionBuilder::default()
        .to_address(receiver.address.clone())
        .amount(parse_zil("2.0")?)
        .gas_price(2000000000u128)
        .gas_limit(50u64)
        .build();

这里我们将转移 2.0 ZIL 到接收者。现在我们需要发送这个交易

    provider
        .send_transaction_without_confirm::<CreateTransactionResponse>(tx)
        .await?;

现在,让我们检查余额

    let balance = provider.get_balance(&receiver.address).await;
    println!("{balance:?}");
cargo run

Ok(BalanceResponse { nonce: 138, balance: 899999994124734000000000 })
Ok(BalanceResponse { nonce: 0, balance: 2000000000000 })

使用 pay 函数

TransactionBuilder 有一个辅助函数 pay,用于简化支付交易创建

    let tx = TransactionBuilder::default().pay(amount, receiver.address.clone()).build();

处理合约

技术笔记

zilliqa-rs 的一大亮点是在构建时为您的 scilla 合同生成 Rust 代码。这意味着如果您合同中有一个类似 transfer 的转换,您可以像调用普通 Rust 函数一样调用它。如果它有一个地址参数,您必须向此函数传递地址。这意味着 Rust 的类型检查之美都可以在处理 scilla 合同时发挥作用。

从 scilla 合同生成 Rust 代码

我们想要部署一个名为 HelloWorld 的简单合同并调用其 setHello 转换。首先,我们需要在 src 旁边创建一个文件夹。让我们称它为 contracts。然后我们将 HelloWorld.scilla 移动到这个文件夹。为了让 zilliqa-rs 知道 scilla 到 Rust 代码生成的合同路径,我们需要导出 CONTRACTS_PATH 环境变量。最简单的方法是创建 .cargo/config.toml 文件并更改它如下:

[env]
CONTRACTS_PATH = {value = "contracts", relative = true}

relative 设置为 true 是至关重要的。否则,您的 scilla 合同不会编译成 Rust。现在,如果您使用 cargo build 构建项目,HelloWorld.scilla 就会在幕后转换为 Rust。

生成的代码可能如下所示

impl<T: Middleware> HelloWorld<T> {
    pub async fn deploy(client: Arc<T> , owner: ZilAddress) -> Result<Self, Error> {
    }

    pub fn address(&self) -> &ZilAddress  {
    }
    
    pub fn set_hello(&self , msg: String) -> RefMut<'_, transition_call::TransitionCall<T>> {
    }

    pub fn get_hello(&self ) -> RefMut<'_, transition_call::TransitionCall<T>> {
    }

    pub async fn welcome_msg(&self) -> Result<String, Error> {
    }

    pub async fn owner(&self) -> Result<ZilAddress, Error> {
    }
}
  • deploy 将合同部署到网络。由于 HelloWorld.scilla 合同接受一个地址 owner 作为部署参数,因此 deploy 函数也需要它。这意味着您必须提供有效地址才能部署它。
  • address 函数返回已部署合同的地址。
  • set_hello 对应于合同中的 setHello 转换。同样,由于转换接受一个字符串参数,因此 set_hello 函数也是如此。
  • get_hello 对应于 getHello 转换。
  • 合同有一个名为 welcome_msg 的字段,要获取此字段的值,应调用 welcome_msg 函数。
  • 合同有一个名为 owner 的不可变状态,我们在部署时传递了值。要获取所有者值,需要调用 owner

合同部署

现在是部署合同的时候了

    let contract = HelloWorld::deploy(provider.into(), wallet.address).await?;
    println!("Contract address: {:?}", contract.address());

deploy 的第一个参数是提供者。其余的取决于合同及其具有多少不可变状态。在这里,HelloWorld.scilla 我们只有 owner,所以我们只传递一个地址。它是类型安全的,这意味着您不能将整数或原始字符串传递给 deploy 函数作为 owner

运行代码

cargo run

Ok(BalanceResponse { nonce: 138, balance: 899999994124734000000000 })
Contract address: ZilAddress("0xC50C93831F6eAB4e4F011076dca6e887288cc872")

如果您喜欢部署压缩版本的合同,可以使用 deploy_compressed 而不是 deploy

获取合同状态

我们的合同有一个不可变状态 owner 和一个可变状态 welcome_msg。我们可以通过调用相应的函数来获取这些状态。

    println!("Contract owner: {:?}", contract.owner().await?);
    println!("Welcome msg: {}", contract.welcome_msg().await?);

调用转换

我们的合同有一个 setHello 转换。调用这个转换并不比调用 Rust 函数困难。

    contract.set_hello("Salaam".to_string()).call().await?;

请注意,这里我们需要调用 call。这是因为您在调用 call 之前所做的所有操作都像是配置转换调用。例如,您可以在调用 call 函数之前设置想要传递给转换的 ZIL 数量。

    contract.transfer(receiver).amount(parse_zil("0.1")).call().await?;

好的,现在如果您获取并打印 welcome_msg,它应该有新的值

    println!("Welcome msg: {}", contract.welcome_msg().await?);

最终的 main

use std::error::Error;

use zilliqa_rs::{
    contract::HelloWorld,
    core::CreateTransactionResponse,
    middlewares::Middleware,
    providers::{Http, Provider},
    signers::LocalWallet,
    transaction::TransactionBuilder,
    core::parse_zil,
};

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    // Create the signer.
    let wallet = "0xe53d1c3edaffc7a7bab5418eb836cf75819a82872b4a1a0f1c7fcf5c3e020b89"
        .parse::<LocalWallet>()?;

    // Create the provider with a signer.
    let provider = Provider::<Http>::try_from("http://127.0.0.1:5555")?
        .with_chain_id(222)
        .with_signer(wallet.clone());

    // Call a JSON-RPC endpoint.
    let balance = provider
        .get_balance("0x381f4008505e940ad7681ec3468a719060caf796")
        .await;

    println!("{balance:?}");

    // Send a transaction
    let receiver = LocalWallet::create_random()?;
    let tx = TransactionBuilder::default()
        .to_address(receiver.address.clone())
        .amount(parse_zil("2.0")?)
        .gas_price(2000000000u128)
        .gas_limit(50u64)
        .build();

    provider
        .send_transaction_without_confirm::<CreateTransactionResponse>(tx)
        .await?;

    let balance = provider.get_balance(&receiver.address).await;
    println!("{balance:?}");

    // Deploy a contract
    let contract = HelloWorld::deploy(provider.into(), wallet.address).await?;
    println!("Contract address: {:?}", contract.address());

    println!("Contract owner: {:?}", contract.owner().await?);
    println!("Welcome msg: {}", contract.welcome_msg().await?);

    contract.set_hello("Salaam".to_string()).call().await?;
    println!("Welcome msg: {}", contract.welcome_msg().await?);
    Ok(())
}

让我们运行代码

cargo run

Ok(BalanceResponse { nonce: 138, balance: 899999994124734000000000 })
Contract address: ZilAddress("0xB84De4A67E1640D9259c502AAb6751678B593185")
Contract owner: ZilAddress("0xd90f2e538CE0Df89c8273CAd3b63ec44a3c4ed82")
Welcome msg: Hello world!
Welcome msg: Salaam

依赖项

~18–31MB
~471K SLoC