Ethers.rs Rust库¶
概览¶
Ethers.rs库提供一套工具,通过Rust编程语言与以太坊节点交互,其运作方式与Ethers.js相似。Moonbeam拥有类似以太坊的API,能够与以太坊式的JSON-RPC调用完全兼容。因此,开发者可以利用此兼容性并使用Ethers.rs库如同与以太坊一样与Moonbeam节点交互。您可以在其官方文档获取更多关于如何使用Ethers.rs的信息。
在本教程中,您将学习如何使用Ethers.rs库在Moonbase Alpha上发送交易和部署合约。本教程也同样适用于 Moonbeam、Moonriver和Moonbeam开发节点。
查看先决条件¶
在本教程的示例中,您将需要准备以下内容:
- 拥有资金的账户。 您可以每24小时一次从Moonbase Alpha水龙头上获取DEV代币以在Moonbase Alpha上进行测试
- 要在Moonbeam或Moonriver网络上测试本指南中的示例,您可以从受支持的网络端点提供商之一获取您自己的端点和API密钥
- 在设备上安装Rust
- 在设备上安装solc。Ethers.rs包的建议使用solc-select
注意事项
本教程的示例中假设您拥有基于MacOS或Ubuntu 20.04的环境,且需要针对Windows系统进行相应调整。
创建一个Rust项目¶
首先,您可以使用Cargo工具创建一个新的Rust项目:
cargo init ethers-examples && cd ethers-examples
在本教程中,您将需要安装Ethers.rs库等。要在Rust项目中安装,您必须编辑文档中包含的Cargo.toml
文件并将其包含在依赖项中:
[package]
name = "ethers-examples"
version = "0.1.0"
edition = "2021"
[dependencies]
ethers = "1.0.2"
ethers-solc = "1.0.2"
tokio = { version = "1", features = ["full"] }
serde_json = "1.0.89"
serde = "1.0.149"
本示例使用ethers
和ethers-solc
crate版本1.0.2
用于RPC交互和Solidity编译。这也包含了tokio
crate以运行异步Rust环境,因为与RPC交互需要异步代码。最后,这也包含了serde_json
和serde
crates来帮助序列化/反序列化此示例的代码。
如果这是您第一次使用solc-select
,您将需要使用以下命令来安装和配置Solidity版本:
solc-select install 0.8.17 && solc-select use 0.8.17
设置Ethers提供商和客户端¶
在整个教程中,您将编写多个函数,用于提供不同的功能,例如发送交易、部署合约,以及与部署的合约交互。在大部分这些脚本中,您将需要使用Ethers provider或Ethers signer client与网络进行交互。
要为Moonbeam或Moonriver网络配置您的项目,您可以从受支持的网络端点提供商之一获取您自己的端点和API密钥。
创建提供商和签署者有多种方式,但是最简单的方式是通过try_from
操作:
- 从
ethers
crate导入Provider
和Http
- 为了方便操作,添加
Client
类型,当您开始创建发送交易和部署合约的函数时,将会使用此函数 - 在
async fn main()
上方添加tokio
属性,用于异步执行 - 使用
try_from
尝试从RPC端点实例化JSON-RPC提供商对象 - 使用私钥创建钱包对象(私钥将用于签署交易)。请注意:此示例仅用于演示目的,请勿将您的私钥存储于普通的Rust文件中
- 通过将提供商和钱包提供到
SignerMiddleware
对象中,将其包装到客户端中
// 1. Import ethers crate
use ethers::providers::{Provider, Http};
// 2. Add client type
type Client = SignerMiddleware<Provider<Http>, Wallet<k256::ecdsa::SigningKey>>;
// 3. Add annotation
#[tokio::main]
fn main() -> Result<(), Box<dyn std::error::Error>> {
// 4. Use try_from with RPC endpoint
let provider = Provider::<Http>::try_from(
"INSERT_RPC_API_ENDPOINT"
)?;
// 5. Use a private key to create a wallet
// Do not include the private key in plain text in any production code
// This is just for demonstration purposes
// Do not include '0x' at the start of the private key
let wallet: LocalWallet = "INSERT_YOUR_PRIVATE_KEY"
.parse::<LocalWallet>()?
.with_chain_id(Chain::Moonbeam);
// 6. Wrap the provider and wallet together to create a signer client
let client = SignerMiddleware::new(provider.clone(), wallet.clone());
Ok(())
}
// 1. Import ethers crate
use ethers::providers::{Provider, Http};
// 2. Add client type
type Client = SignerMiddleware<Provider<Http>, Wallet<k256::ecdsa::SigningKey>>;
// 3. Add annotation
#[tokio::main]
fn main() -> Result<(), Box<dyn std::error::Error>> {
// 4. Use try_from with RPC endpoint
let provider = Provider::<Http>::try_from(
"INSERT_RPC_API_ENDPOINT"
)?;
// 5. Use a private key to create a wallet
// Do not include the private key in plain text in any production code
// This is just for demonstration purposes
// Do not include '0x' at the start of the private key
let wallet: LocalWallet = "INSERT_YOUR_PRIVATE_KEY"
.parse::<LocalWallet>()?
.with_chain_id(Chain::Moonriver);
// 6. Wrap the provider and wallet together to create a signer client
let client = SignerMiddleware::new(provider.clone(), wallet.clone());
Ok(())
}
// 1. Import ethers crate
use ethers::providers::{Provider, Http};
// 2. Add client type
type Client = SignerMiddleware<Provider<Http>, Wallet<k256::ecdsa::SigningKey>>;
// 3. Add annotation
#[tokio::main]
fn main() -> Result<(), Box<dyn std::error::Error>> {
// 4. Use try_from with RPC endpoint
let provider = Provider::<Http>::try_from(
"https://rpc.api.moonbase.moonbeam.network"
)?;
// 5. Use a private key to create a wallet
// Do not include the private key in plain text in any production code
// This is just for demonstration purposes
// Do not include '0x' at the start of the private key
let wallet: LocalWallet = "INSERT_YOUR_PRIVATE_KEY"
.parse::<LocalWallet>()?
.with_chain_id(Chain::Moonbase);
// 6. Wrap the provider and wallet together to create a signer client
let client = SignerMiddleware::new(provider.clone(), wallet.clone());
Ok(())
}
// 1. Import ethers crate
use ethers::providers::{Provider, Http};
// 2. Add client type
type Client = SignerMiddleware<Provider<Http>, Wallet<k256::ecdsa::SigningKey>>;
// 3. Add annotation
#[tokio::main]
fn main() -> Result<(), Box<dyn std::error::Error>> {
// 4. Use try_from with RPC endpoint
let provider = Provider::<Http>::try_from(
"http://127.0.0.1:9944"
)?;
// 5. Use a private key to create a wallet
// Do not include the private key in plain text in any production code
// This is just for demonstration purposes
// Do not include '0x' at the start of the private key
let wallet: LocalWallet = "INSERT_YOUR_PRIVATE_KEY"
.parse::<LocalWallet>()?
.with_chain_id(Chain::MoonbeamDev);
// 6. Wrap the provider and wallet together to create a signer client
let client = SignerMiddleware::new(provider.clone(), wallet.clone());
Ok(())
}
发送交易¶
在这一部分中,您将创建几个函数,这将包含在同一个main.rs
文件中,以避免从实现模块带来的额外复杂性。第一个函数将在尝试发送交易前检查账户余额。第二个函数将实际发送交易。要运行这些函数,您需要编辑main
函数并运行main.rs
脚本。
您应该根据上述部分所描述的方法在main.rs
中设置了提供商和客户端。要发送交易,您将需要添加几行代码:
- 在您的导入中添加
use ethers::{utils, prelude::*};
,这将为您提供访问实用程序函数的权限,并且prelude导入所有必要的数据类型和特征 - 因为您将从一个地址发送交易至另一个地址,您可以在
main
函数中指定发送和接收地址。请注意:address_from
值应该对应于main
函数中所使用的私钥
// ...
// 1. Add to imports
use ethers::{utils, prelude::*};
// ...
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// ...
// 2. Add from and to address
let address_from = "YOUR FROM ADDRESS".parse::<Address>()?
let address_to = "YOUR TO ADDRESS".parse::<Address>()?
}
查看余额函数¶
接下来,您将通过执行以下步骤创建函数以获取发送和接收账户的余额:
- 创建一个名为
print_balances
的新异步函数,这将提供商对象的引用以及发送和接收地址作为输入 - 使用
provider
对象的get_balance
函数以获取交易发送和接收地址的余额 - 输出发送和接收地址的结果余额
- 在
main
函数中调用print_balances
函数
// ...
// 1. Create an asynchronous function that takes a provider reference and from and to address as input
async fn print_balances(provider: &Provider<Http>, address_from: Address, address_to: Address) -> Result<(), Box<dyn std::error::Error>> {
// 2. Use the get_balance function
let balance_from = provider.get_balance(address_from, None).await?;
let balance_to = provider.get_balance(address_to, None).await?;
// 3. Print the resultant balance
println!("{} has {}", address_from, balance_from);
println!("{} has {}", address_to, balance_to);
Ok(())
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// ...
// 4. Call print_balances function in main
print_balances(&provider).await?;
Ok(())
}
发送交易脚本¶
在本示例中,您将从源地址(即您持有私钥的地址)发送1 DEV至另一个地址。
- 创建一个名为
send_transaction
的新异步函数,这将客户端对象的引用以及发送和接收地址作为输入 - 创建交易对象,并包含
to
、value
和from
。当编写value
输入时,使用ethers::utils::parse_ether
函数 - 使用
client
对象来发送交易 - 交易确认后输出交易
- 在
main
函数中调用send_transaction
函数
// ...
// 1. Define an asynchronous function that takes a client provider and the from and to addresses as input
async fn send_transaction(client: &Client, address_from: Address, address_to: Address) -> Result<(), Box<dyn std::error::Error>> {
println!(
"Beginning transfer of 1 native currency from {} to {}.",
address_from, address_to
);
// 2. Create a TransactionRequest object
let tx = TransactionRequest::new()
.to(address_to)
.value(U256::from(utils::parse_ether(1)?))
.from(address_from);
// 3. Send the transaction with the client
let tx = client.send_transaction(tx, None).await?.await?;
// 4. Print out the result
println!("Transaction Receipt: {}", serde_json::to_string(&tx)?);
Ok(())
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// ...
// 5. Call send_transaction function in main
send_transaction(&client, address_from, address_to).await?;
Ok(())
}
您可以
use ethers::providers::{Provider, Http};
use ethers::{utils, prelude::*};
type Client = SignerMiddleware<Provider<Http>, Wallet<k256::ecdsa::SigningKey>>;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let provider: Provider<Http> = Provider::<Http>::try_from("https://rpc.api.moonbase.moonbeam.network")?; // Change to correct network
// Do not include the private key in plain text in any production code. This is just for demonstration purposes
let wallet: LocalWallet = "INSERT_PRIVATE_KEY"
.parse::<LocalWallet>()?
.with_chain_id(Chain::Moonbase); // Change to correct network
let client = SignerMiddleware::new(provider.clone(), wallet.clone());
let address_from = "INSERT_FROM_ADDRESS".parse::<Address>()?;
let address_to = "INSERT_TO_ADDRESS".parse::<Address>()?;
send_transaction(&client, &address_from, &address_to).await?;
print_balances(&provider, &address_from, &address_to).await?;
Ok(())
}
// Print the balance of a wallet
async fn print_balances(provider: &Provider<Http>, address_from: &Address, address_to: &Address) -> Result<(), Box<dyn std::error::Error>> {
let balance_from = provider.get_balance(address_from.clone(), None).await?;
let balance_to = provider.get_balance(address_to.clone(), None).await?;
println!("{} has {}", address_from, balance_from);
println!("{} has {}", address_to, balance_to);
Ok(())
}
// Sends some native currency
async fn send_transaction(client: &Client, address_from: &Address, address_to: &Address) -> Result<(), Box<dyn std::error::Error>> {
println!(
"Beginning transfer of 1 native currency {} to {}.",
address_from, address_to
);
let tx = TransactionRequest::new()
.to(address_to.clone())
.value(U256::from(utils::parse_ether(1)?))
.from(address_from.clone());
let tx = client.send_transaction(tx, None).await?.await?;
println!("Transaction Receipt: {}", serde_json::to_string(&tx)?);
Ok(())
}
要运行发送交易并在交易发送后检查余额的脚本,您可以运行以下命令:
cargo run
如果交易成功后,您将在终端看到交易详情以及地址余额。
部署合约¶
在这一部分中,您将创建几个函数,这将包含在main.rs
文件中,以避免从实现模块带来的额外复杂性。第一个函数将编译和部署合约。剩下的函数将用于与部署的合约交互。
您应该根据设置Ethers提供商和客户端部分所描述的方法在main.rs
中设置了提供商和客户端。
在开始部署合约之前,您将需要添加一些导入至main.rs
文件中:
use ethers_solc::Solc;
use ethers::{prelude::*};
use std::{path::Path, sync::Arc};
ethers_solc
导入将用于编译智能合约。Ethers的prelude
导入一些必要数据类型和特征。最后,std
导入将使您能够存储智能合约并将客户端包装成Arc
类型以实现线程安全。
编译和部署合约脚本¶
此示例函数将编译和部署您在上述部分中创建的Incrementer.sol
智能合约。Incrementer.sol
智能合约需要在根目录中。在main.rs
文件中,请执行以下步骤:
- 创建名为
compile_deploy_contract
的新异步函数,这将客户端对象的引用作为输入,并返回H160
格式的地址 - 定义名为
source
的变量作为托管所有需要编译的智能合约的目录路径,该目录为根目录 - 在根目录中使用
Solc
crate编译所有的智能合约 - 从编译的结果中获取ABI和字节码,搜索
Incrementer.sol
合约 - 使用ABI、字节码和客户端从智能合约创建一个合约工厂。客户端必须包装成
Arc
类型以实现线程安全 - 使用工厂部署。在本示例中,在构造函数处以
5
作为初始值 - 部署后输出地址
- 返回地址
- 在
main
函数中调用compile_deploy_contract
函数
// ...
// 1. Define an asynchronous function that takes a client provider as input and returns H160
async fn compile_deploy_contract(client: &Client) -> Result<H160, Box<dyn std::error::Error>> {
// 2. Define a path as the directory that hosts the smart contracts in the project
let source = Path::new(&env!("CARGO_MANIFEST_DIR"));
// 3. Compile all of the smart contracts
let compiled = Solc::default()
.compile_source(source)
.expect("Could not compile contracts");
// 4. Get ABI & Bytecode for Incrementer.sol
let (abi, bytecode, _runtime_bytecode) = compiled
.find("Incrementer")
.expect("could not find contract")
.into_parts_or_default();
// 5. Create a contract factory which will be used to deploy instances of the contract
let factory = ContractFactory::new(abi, bytecode, Arc::new(client.clone()));
// 6. Deploy
let contract = factory.deploy(U256::from(5))?.send().await?;
// 7. Print out the address
let addr = contract.address();
println!("Incrementer.sol has been deployed to {:?}", addr);
// 8. Return the address
Ok(addr)
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// ...
// 9. Call compile_deploy_contract function in main
let addr = compile_deploy_contract(&client).await?;
Ok(())
}
读取合约数据(调用函数)¶
调用函数是一种不修改合约存储(更改变量)的交互类型,这意味着无需发送交易。他们只读取已部署合约的各类存储变量。
Rust是typesafe,这就是为什么需要Incrementer.sol
合约的ABI来生成typesafe Rust结构。在本示例中,您应该在名为Incrementer_ABI.json
的Cargo项目的根目录中创建一个新文件:
touch Incrementer_ABI.json
Incrementer.sol
的ABI如下所示,复制并将其粘贴至Incrementer_ABI.json
文件中:
[
{
"inputs": [
{
"internalType": "uint256",
"name": "_value",
"type": "uint256"
}
],
"name": "increment",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "number",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "reset",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]
然后,请执行以下步骤创建一个可以读取并返回Incrementer.sol
合约number
函数的函数:
- 使用
abigen
macro为Incrementer
智能合约生成一个type-safe接口 - 创建一个名为
read_number
的新异步函数,这将客户端对象的引用和合约地址引用作为输入,并返回U256 - 使用客户端和合约地址值创建一个由abigen macro生成的
Incrementer
对象的新实例 - 在新的
Incrementer
对象中调用number
函数 - 输出结果值
- 返回结果值
- 在
main
函数中调用read_number
函数
// ...
// 1. Generate a type-safe interface for the Incrementer smart contract
abigen!(
Incrementer,
"./Incrementer_ABI.json",
event_derives(serde::Deserialize, serde::Serialize)
);
// 2. Define an asynchronous function that takes a client provider and address as input and returns a U256
async fn read_number(client: &Client, contract_addr: &H160) -> Result<U256, Box<dyn std::error::Error>> {
// 3. Create contract instance
let contract = Incrementer::new(contract_addr.clone(), Arc::new(client.clone()));
// 4. Call contract's number function
let value = contract.number().call().await?;
// 5. Print out number
println!("Incrementer's number is {}", value);
// 6. Return the number
Ok(value)
}
// ...
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// ...
// 7. Call read_number function in main
read_number(&client, &addr).await?;
Ok(())
}
您可以
use ethers::providers::{Provider, Http};
use ethers::{prelude::*};
use ethers_solc::Solc;
use std::{path::Path, sync::Arc};
type Client = SignerMiddleware<Provider<Http>, Wallet<k256::ecdsa::SigningKey>>;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let provider: Provider<Http> = Provider::<Http>::try_from("https://rpc.api.moonbase.moonbeam.network")?; // Change to correct network
// Do not include the private key in plain text in any production code. This is just for demonstration purposes
// Do not include '0x' at the start of the private key
let wallet: LocalWallet = "INSERT_PRIVATE_KEY"
.parse::<LocalWallet>()?
.with_chain_id(Chain::Moonbase);
let client = SignerMiddleware::new(provider.clone(), wallet.clone());
// Deploy contract and read initial incrementer value
let addr = compile_deploy_contract(&client).await?;
read_number(&client, &addr).await?;
// Increment and read the incremented number
increment_number(&client, &addr).await?;
read_number(&client, &addr).await?;
// Reset the incremented number and read it
reset(&client, &addr).await?;
read_number(&client, &addr).await?;
Ok(())
}
// Need to install solc for this tutorial: https://github.com/crytic/solc-select
async fn compile_deploy_contract(client: &Client) -> Result<H160, Box<dyn std::error::Error>> {
// Incrementer.sol is located in the root directory
let source = Path::new(&env!("INSERT_CARGO_MANIFEST_DIR"));
// Compile it
let compiled = Solc::default()
.compile_source(source)
.expect("Could not compile contracts");
// Get ABI & Bytecode for Incrementer.sol
let (abi, bytecode, _runtime_bytecode) = compiled
.find("Incrementer")
.expect("could not find contract")
.into_parts_or_default();
// Create a contract factory which will be used to deploy instances of the contract
let factory = ContractFactory::new(abi, bytecode, Arc::new(client.clone()));
// Deploy
let contract = factory.deploy(U256::from(5))?.send().await?;
let addr = contract.address();
println!("Incrementer.sol has been deployed to {:?}", addr);
Ok(addr)
}
// Generates a type-safe interface for the Incrementer smart contract
abigen!(
Incrementer,
"./Incrementer_ABI.json",
event_derives(serde::Deserialize, serde::Serialize)
);
async fn read_number(client: &Client, contract_addr: &H160) -> Result<U256, Box<dyn std::error::Error>> {
// Create contract instance
let contract = Incrementer::new(contract_addr.clone(), Arc::new(client.clone()));
// Call contract's number function
let value = contract.number().call().await?;
// Print out value
println!("Incrementer's number is {}", value);
Ok(value)
}
async fn increment_number(client: &Client, contract_addr: &H160) -> Result<(), Box<dyn std::error::Error>> {
println!("Incrementing number...");
// Create contract instance
let contract = Incrementer::new(contract_addr.clone(), Arc::new(client.clone()));
// Send contract transaction
let tx = contract.increment(U256::from(5)).send().await?.await?;
println!("Transaction Receipt: {}", serde_json::to_string(&tx)?);
Ok(())
}
async fn reset(client: &Client, contract_addr: &H160) -> Result<(), Box<dyn std::error::Error>> {
println!("Resetting number...");
// Create contract instance
let contract = Incrementer::new(contract_addr.clone(), Arc::new(client.clone()));
// Send contract transaction
let tx = contract.reset().send().await?.await?;
println!("Transaction Receipt: {}", serde_json::to_string(&tx)?);
Ok(())
}
要运行部署合约和返回存储在Incrementer
合约中的当前值的脚本,您可以在终端中输入以下命令:
cargo run
如果成功,您将在终端中看到已部署合约的地址和初始值(应为5
)
与合约交互(发送函数)¶
发送函数是一种修改合约存储(更改变量)的交互类型,这意味着需要签署和发送交易。在这一部分中,您将创建两个函数:一个为increment,另一个为重置incrementer。此部分还将需要在从智能合约读取时初始化Incrementer_ABI.json
文件。
执行以下步骤创建函数以递增:
- 确保在
main.rs
文件中为Incrementer_ABI.json
调用了abigen macro(如果它已存在于main.rs
文件中,则无需再有第二个) - 创建一个名为
increment_number
的新异步函数,这将客户端对象的引用和地址作为输入 - 使用客户端和合约地址值创建一个由abigen macro生成的
Incrementer
对象的新实例 - 通过将
U256
对象作为输入值包含在新的Incrementer
对象中调用increment
函数。在本示例中,此数值为5
- 在
main
函数调用read_number
函数
// ...
// 1. Generate a type-safe interface for the Incrementer smart contract
abigen!(
Incrementer,
"./Incrementer_ABI.json",
event_derives(serde::Deserialize, serde::Serialize)
);
// 2. Define an asynchronous function that takes a client provider and address as input
async fn increment_number(client: &Client, contract_addr: &H160) -> Result<(), Box<dyn std::error::Error>> {
println!("Incrementing number...");
// 3. Create contract instance
let contract = Incrementer::new(contract_addr.clone(), Arc::new(client.clone()));
// 4. Send contract transaction
let tx = contract.increment(U256::from(5)).send().await?.await?;
println!("Transaction Receipt: {}", serde_json::to_string(&tx)?);
Ok(())
}
// ...
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// ...
// 5. Call increment_number function in main
increment_number(&client, &addr).await?;
Ok(())
}
您可以
use ethers::providers::{Provider, Http};
use ethers::{prelude::*};
use ethers_solc::Solc;
use std::{path::Path, sync::Arc};
type Client = SignerMiddleware<Provider<Http>, Wallet<k256::ecdsa::SigningKey>>;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let provider: Provider<Http> = Provider::<Http>::try_from("https://rpc.api.moonbase.moonbeam.network")?; // Change to correct network
// Do not include the private key in plain text in any production code. This is just for demonstration purposes
// Do not include '0x' at the start of the private key
let wallet: LocalWallet = "INSERT_PRIVATE_KEY"
.parse::<LocalWallet>()?
.with_chain_id(Chain::Moonbase);
let client = SignerMiddleware::new(provider.clone(), wallet.clone());
// Deploy contract and read initial incrementer value
let addr = compile_deploy_contract(&client).await?;
read_number(&client, &addr).await?;
// Increment and read the incremented number
increment_number(&client, &addr).await?;
read_number(&client, &addr).await?;
// Reset the incremented number and read it
reset(&client, &addr).await?;
read_number(&client, &addr).await?;
Ok(())
}
// Need to install solc for this tutorial: https://github.com/crytic/solc-select
async fn compile_deploy_contract(client: &Client) -> Result<H160, Box<dyn std::error::Error>> {
// Incrementer.sol is located in the root directory
let source = Path::new(&env!("INSERT_CARGO_MANIFEST_DIR"));
// Compile it
let compiled = Solc::default()
.compile_source(source)
.expect("Could not compile contracts");
// Get ABI & Bytecode for Incrementer.sol
let (abi, bytecode, _runtime_bytecode) = compiled
.find("Incrementer")
.expect("could not find contract")
.into_parts_or_default();
// Create a contract factory which will be used to deploy instances of the contract
let factory = ContractFactory::new(abi, bytecode, Arc::new(client.clone()));
// Deploy
let contract = factory.deploy(U256::from(5))?.send().await?;
let addr = contract.address();
println!("Incrementer.sol has been deployed to {:?}", addr);
Ok(addr)
}
// Generates a type-safe interface for the Incrementer smart contract
abigen!(
Incrementer,
"./Incrementer_ABI.json",
event_derives(serde::Deserialize, serde::Serialize)
);
async fn read_number(client: &Client, contract_addr: &H160) -> Result<U256, Box<dyn std::error::Error>> {
// Create contract instance
let contract = Incrementer::new(contract_addr.clone(), Arc::new(client.clone()));
// Call contract's number function
let value = contract.number().call().await?;
// Print out value
println!("Incrementer's number is {}", value);
Ok(value)
}
async fn increment_number(client: &Client, contract_addr: &H160) -> Result<(), Box<dyn std::error::Error>> {
println!("Incrementing number...");
// Create contract instance
let contract = Incrementer::new(contract_addr.clone(), Arc::new(client.clone()));
// Send contract transaction
let tx = contract.increment(U256::from(5)).send().await?.await?;
println!("Transaction Receipt: {}", serde_json::to_string(&tx)?);
Ok(())
}
async fn reset(client: &Client, contract_addr: &H160) -> Result<(), Box<dyn std::error::Error>> {
println!("Resetting number...");
// Create contract instance
let contract = Incrementer::new(contract_addr.clone(), Arc::new(client.clone()));
// Send contract transaction
let tx = contract.reset().send().await?.await?;
println!("Transaction Receipt: {}", serde_json::to_string(&tx)?);
Ok(())
}
要运行脚本,您可以在终端输入以下命令:
cargo run
如果成功,交易收据将会显示在终端显示。您可以在main
函数中使用read_number
函数,以确保数值按预期变化。如果您在递增后使用read_number
函数,您也会看到递增的数字,该数值应为10
。
接下来,您可以与reset
函数进行交互:
- 确保在
main.rs
文件中为Incrementer_ABI.json
调用了abigen macro(如果它已存在于main.rs
文件中,则无需再有第二个) - 创建一个名为
reset
的新异步函数,这将客户端对象的引用和地址作为输入 - 使用客户端和合约地址值创建一个由abigen macro生成的
Incrementer
对象的新实例 - 在新的
Incrementer
对象中调用reset
函数 - 在
main
函数中调用reset
函数
// ...
// 1. Generate a type-safe interface for the Incrementer smart contract
abigen!(
Incrementer,
"./Incrementer_ABI.json",
event_derives(serde::Deserialize, serde::Serialize)
);
// 2. Define an asynchronous function that takes a client provider and address as input
async fn reset(client: &Client, contract_addr: &H160) -> Result<(), Box<dyn std::error::Error>> {
println!("Resetting number...");
// 3. Create contract instance
let contract = Incrementer::new(contract_addr.clone(), Arc::new(client.clone()));
// 4. Send contract transaction
let tx = contract.reset().send().await?.await?;
println!("Transaction Receipt: {}", serde_json::to_string(&tx)?);
Ok(())
}
// ...
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// ...
// 5. Call reset function in main
reset(&client, &addr).await?;
Ok(())
}
如果成功,交易收据将会显示在终端显示。您可以在main
函数中使用read_number
函数,以确保数值按预期变化。如果您在重置数值后使用read_number
函数,您应在终端看到0
。
您可以
use ethers::providers::{Provider, Http};
use ethers::{prelude::*};
use ethers_solc::Solc;
use std::{path::Path, sync::Arc};
type Client = SignerMiddleware<Provider<Http>, Wallet<k256::ecdsa::SigningKey>>;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let provider: Provider<Http> = Provider::<Http>::try_from("https://rpc.api.moonbase.moonbeam.network")?; // Change to correct network
// Do not include the private key in plain text in any production code. This is just for demonstration purposes
// Do not include '0x' at the start of the private key
let wallet: LocalWallet = "INSERT_PRIVATE_KEY"
.parse::<LocalWallet>()?
.with_chain_id(Chain::Moonbase);
let client = SignerMiddleware::new(provider.clone(), wallet.clone());
// Deploy contract and read initial incrementer value
let addr = compile_deploy_contract(&client).await?;
read_number(&client, &addr).await?;
// Increment and read the incremented number
increment_number(&client, &addr).await?;
read_number(&client, &addr).await?;
// Reset the incremented number and read it
reset(&client, &addr).await?;
read_number(&client, &addr).await?;
Ok(())
}
// Need to install solc for this tutorial: https://github.com/crytic/solc-select
async fn compile_deploy_contract(client: &Client) -> Result<H160, Box<dyn std::error::Error>> {
// Incrementer.sol is located in the root directory
let source = Path::new(&env!("INSERT_CARGO_MANIFEST_DIR"));
// Compile it
let compiled = Solc::default()
.compile_source(source)
.expect("Could not compile contracts");
// Get ABI & Bytecode for Incrementer.sol
let (abi, bytecode, _runtime_bytecode) = compiled
.find("Incrementer")
.expect("could not find contract")
.into_parts_or_default();
// Create a contract factory which will be used to deploy instances of the contract
let factory = ContractFactory::new(abi, bytecode, Arc::new(client.clone()));
// Deploy
let contract = factory.deploy(U256::from(5))?.send().await?;
let addr = contract.address();
println!("Incrementer.sol has been deployed to {:?}", addr);
Ok(addr)
}
// Generates a type-safe interface for the Incrementer smart contract
abigen!(
Incrementer,
"./Incrementer_ABI.json",
event_derives(serde::Deserialize, serde::Serialize)
);
async fn read_number(client: &Client, contract_addr: &H160) -> Result<U256, Box<dyn std::error::Error>> {
// Create contract instance
let contract = Incrementer::new(contract_addr.clone(), Arc::new(client.clone()));
// Call contract's number function
let value = contract.number().call().await?;
// Print out value
println!("Incrementer's number is {}", value);
Ok(value)
}
async fn increment_number(client: &Client, contract_addr: &H160) -> Result<(), Box<dyn std::error::Error>> {
println!("Incrementing number...");
// Create contract instance
let contract = Incrementer::new(contract_addr.clone(), Arc::new(client.clone()));
// Send contract transaction
let tx = contract.increment(U256::from(5)).send().await?.await?;
println!("Transaction Receipt: {}", serde_json::to_string(&tx)?);
Ok(())
}
async fn reset(client: &Client, contract_addr: &H160) -> Result<(), Box<dyn std::error::Error>> {
println!("Resetting number...");
// Create contract instance
let contract = Incrementer::new(contract_addr.clone(), Arc::new(client.clone()));
// Send contract transaction
let tx = contract.reset().send().await?.await?;
println!("Transaction Receipt: {}", serde_json::to_string(&tx)?);
Ok(())
}