本文介绍了如何使用Mostu测试套件对Arbitrum Stylus上的Rust智能合约进行单元测试。Mostu简化了测试流程,允许开发者像编写常规Rust测试一样进行智能合约测试,并通过[motsu::test]宏提供对VM的支持。文章详细说明了如何创建一个新项目、集成合约库以及设置测试环境,并提供了一些示例测试用例。
你可能已经熟悉我们在 2024 年 10 月发布的 Stylus 合约库。虽然 Arbitrum Stylus 提供了更快的执行速度和更低的 gas 费用,但用于单元测试智能合约的工具却很有限。测试对于确保智能合约按预期运行并最大限度地降低漏洞风险至关重要。区块链技术的不可变性需要严格的安全措施,因此彻底的测试是开发过程中至关重要的一部分。
为了满足这一需求,我们开发了 Mostu,这是一个用于 Rust 智能合约的实用测试套件。Mostu 简化了编写测试的过程,使开发人员能够确保其代码的可靠性。在本指南中,我们将引导你完成创建新项目、集成我们的合约库以及设置测试环境的步骤。
使用 Motsu 在 Rust 中进行更智能测试的分步指南
1 - 安装 Rust
如果你没有安装它,请按照这些说明进行操作。
2 - 设置环境
在终端中运行以下命令,将 Rust 版本设置为 1.80 并安装 Stylus cli。
rustup default 1.80
cargo install --force cargo-stylus
3 - 创建并验证项目
运行此命令以在当前文件夹中初始化一个最小项目,避免额外的文件和文件夹,并具有清晰的结构以开始。
cargo stylus new MyToken --minimal
添加构建目标,以确保与所需部署环境的兼容性以及编译合约的正常运行。
rustup target add wasm32-unknown-unknown
将以下依赖项添加到你的 Cargo.toml
[dependencies]
stylus-sdk = "0.6.0"
openzeppelin-stylus = "0.1.1"
alloy-sol-types = "0.7.6"
在 src/lib.rs 中编写你的合约代码。此示例演示了一个简单的 ERC-20 合约的实现,该合约具有元数据(名称和符号)以及仅所有者权限来铸造代币。代码的初始部分定义了合约可能抛出的错误,这些错误也将用于测试期间的验证。最后一部分实现了代币合约,继承了 openzeppelin_stylus 库中三个合约的功能:Erc20、Erc20Metadata 和 Ownable。
use openzeppelin_stylus::{access::ownable::Ownable, token::erc20::{extensions::Erc20Metadata, Erc20}};
use alloy_sol_types::sol;
use stylus_sdk::{alloy_primitives::{Address, U256}, prelude::{entrypoint, public, sol_storage}, stylus_proc::SolidityError};
// 错误定义,在合约应该失败时进行测试非常重要
sol! {
#[derive(Debug)]
#[allow(missing_docs)]
error OwnableUnauthorizedAccount(address account);
#[derive(Debug)]
#[allow(missing_docs)]
error OwnableErrorAccount(address account);
#[derive(Debug)]
#[allow(missing_docs)]
error MintInvalidReceiver(address receiver);
#[derive(Debug)]
#[allow(missing_docs)]
error MintOperationError(address account, uint256 value);
}
#[derive(SolidityError, Debug)]
pub enum Error {
UnauthorizedAccount(OwnableUnauthorizedAccount),
OwnableError(OwnableErrorAccount),
InvalidReceiver(MintInvalidReceiver),
MintError(MintOperationError),
}
// Token implementation
#[entrypoint]
#[storage]
struct Erc20Example {
#[borrow]
pub erc20: Erc20,
#[borrow]
pub metadata: Erc20Metadata,
#[borrow]
pub ownable: Ownable,
}
#[public]
#[inherit(Erc20,Erc20Metadata,Ownable)]
impl Erc20Example {
// 添加代币铸造功能并验证所有权
pub fn mint(
&mut self,
account: Address,
value: U256,
) -> Result<(), Error> {
self.ownable.only_owner().map_err(|err| match err {
openzeppelin_stylus::access::ownable::Error::UnauthorizedAccount(_) =>
Error::UnauthorizedAccount(OwnableUnauthorizedAccount{account}),
_=> Error::OwnableError(OwnableErrorAccount{account}),
})?;
self.erc20._mint(account, value).map_err(|err| match err {
openzeppelin_stylus::token::erc20::Error::InvalidReceiver(_) =>
Error::InvalidReceiver(MintInvalidReceiver{receiver: account}),
_=> Error::MintError(MintOperationError{account,value}),
})?;
Ok(())
}
}
通过在终端中运行以下命令来验证代码
cargo stylus check
由于 Stylus 中的测试目前受到限制,因此创建了 Motsu 来提供一组专门用于测试 OpenZeppelin Contracts for Stylus 的助手,并且它已作为开源独立 crate 发布给社区。借助 Motsu,你可以像编写常规 Rust 测试一样为合约编写单元测试,从而抽象出必要的设置并通过 #[motsu::test] 宏访问 VM 功能。
首先,将库添加到 Cargo.toml
[dependencies]
...
motsu = "0.2.1"
从一个简单的、简短的测试开始,以确保测试环境设置正确。
这是一个示例,用于验证当没有资金时 balance_of 返回零余额。
#[cfg(test)]
mod tests {
use openzeppelin_stylus::token::erc20::IErc20;
use stylus_sdk::{alloy_primitives::{address, uint}, msg};
use crate::Erc20Example;
#[motsu::test]
fn initial_balance_is_zero(contract: Erc20Example) {
let test_address = address!("1234567891234567891234567891234567891234");
let zero = uint!(0_U256);
let balance = contract.erc20.balance_of(test_address);
assert_eq!(balance, zero);
}
}
一切正常后,为你已在合约中实现的每个函数添加测试。使用各种输入值进行测试,并确保这些函数在边缘情况下按预期失败。无需测试继承的合约,如 ERC20、ERC20Metadata 或 Ownable,因为它们已经通过 Motsu 进行了全面测试。
下面,你将找到三个示例测试:一个用于验证所有者可以铸造代币,另一个用于检查是否可以将代币铸造到零地址,最后一个用于确认非所有者帐户无法铸造代币。
例如 1 验证所有者可以铸造代币
#[motsu::test]
fn owner_mints_tokens(contract: Erc20Example) {
let test_address = address!("1234567891234567891234567891234567891234");
let ten = uint!(10_U256);
contract.ownable._owner.set(msg::sender());
let _ = contract.mint(test_address,ten);
let balance = contract.erc20.balance_of(test_address);
assert_eq!(balance, ten);
}
例如 2 验证是否可以将代币铸造到零地址
#[motsu::test]
fn owner_mints_tokens_to_address_zero(contract: Erc20Example) {
let test_address = Address::ZERO;
let ten = uint!(10_U256);
contract.ownable._owner.set(msg::sender());
let result = contract.mint(test_address,ten);
assert!(matches!(result,Err(Error::InvalidReceiver(_))))
}
例如 3 确认非所有者帐户无法铸造代币。
#[motsu::test]
fn not_owner_tries_to_mint_tokens(contract: Erc20Example) {
let test_address = address!("1234567891234567891234567891234567891234");
let not_owner_address = address!("9123456789123456789123456789123456789123");
let ten = uint!(10_U256);
contract.ownable._owner.set(not_owner_address);
let result = contract.mint(test_address,ten);
assert!(matches!(result,Err(Error::UnauthorizedAccount(_))))
}
通过将 Motsu 集成到你的开发工作流程中,你可以确保你的 Rust 智能合约是健壮、安全且可随时部署的。随着 Stylus 0.7.0 的发布,Motsu 中将提供高度有用的功能,例如事件检查和设置消息发送者的能力。
我们对开源充满热情,并欢迎社区的贡献!此 crate 在尽力而为的基础上进行维护,因为我们在内部测试中广泛使用它。
如果你有想法、建议或反馈,请随时提出 issue 或提交 PR。你还可以查看 existing issues 并参与其中。
- 原文链接: openzeppelin.com/news/te...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!