合约测试
测试在软件开发中起着至关重要的作用,尤其是对于智能合约而言。在本节中,我们将通过Starknet上的scarb
,引导你了解智能合约测试的基础知识。
让我们以一个简单的智能合约作为例子开始:
use starknet::ContractAddress;
#[starknet::interface]
trait ISimpleContract<TContractState> {
fn get_value(self: @TContractState) -> u32;
fn get_owner(self: @TContractState) -> ContractAddress;
fn set_value(ref self: TContractState, value: u32);
}
#[starknet::contract]
mod SimpleContract {
use starknet::{get_caller_address, ContractAddress};
#[storage]
struct Storage {
value: u32,
owner: ContractAddress
}
#[constructor]
fn constructor(ref self: ContractState, initial_value: u32) {
self.value.write(initial_value);
self.owner.write(get_caller_address());
}
#[abi(embed_v0)]
impl SimpleContract of super::ISimpleContract<ContractState> {
fn get_value(self: @ContractState) -> u32 {
self.value.read()
}
fn get_owner(self: @ContractState) -> ContractAddress {
self.owner.read()
}
fn set_value(ref self: ContractState, value: u32) {
assert(self.owner.read() == get_caller_address(), ‘Not owner’);
self.value.write(value);
}
}
}
现在,让我们看一下这个合约的测试:
#[cfg(test)]
mod tests {
// Import the interface and dispatcher to be able to interact with the contract.
use testing_how_to::contract::{
ISimpleContract, SimpleContract, ISimpleContractDispatcher, ISimpleContractDispatcherTrait
};
// Import the deploy syscall to be able to deploy the contract.
use starknet::class_hash::Felt252TryIntoClassHash;
use starknet::{
deploy_syscall, ContractAddress, get_caller_address, get_contract_address,
contract_address_const
};
// Use starknet test utils to fake the transaction context.
use starknet::testing::{set_caller_address, set_contract_address};
// Deploy the contract and return its dispatcher.
fn deploy(initial_value: u32) -> ISimpleContractDispatcher {
// Set up constructor arguments.
let mut calldata = ArrayTrait::new();
initial_value.serialize(ref calldata);
// Declare and deploy
let (contract_address, _) = deploy_syscall(
SimpleContract::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), false
)
.unwrap();
// Return the dispatcher.
// The dispatcher allows to interact with the contract based on its interface.
ISimpleContractDispatcher { contract_address }
}
#[test]
#[available_gas(2000000000)]
fn test_deploy() {
let initial_value: u32 = 10;
let contract = deploy(initial_value);
assert(contract.get_value() == initial_value, 'wrong initial value');
assert(contract.get_owner() == get_contract_address(), 'wrong owner');
}
#[test]
#[available_gas(2000000000)]
fn test_set_as_owner() {
// Fake the caller address to address 1
let owner = contract_address_const::<1>();
set_contract_address(owner);
let contract = deploy(10);
assert(contract.get_owner() == owner, 'wrong owner');
// Fake the contract address to address 1
set_contract_address(owner);
let new_value: u32 = 20;
contract.set_value(new_value);
assert(contract.get_value() == new_value, 'wrong value');
}
#[test]
#[should_panic]
#[available_gas(2000000000)]
fn test_set_not_owner() {
let owner = contract_address_const::<1>();
set_contract_address(owner);
let contract = deploy(10);
let not_owner = contract_address_const::<2>();
set_contract_address(not_owner);
let new_value: u32 = 20;
contract.set_value(new_value);
}
}
Play with this contract in Remix.
为了定义我们的测试,我们使用 scarb,它允许我们创建一个被 #[cfg(test)]
保护的独立模块。这样可以确保测试模块只在使用 scarb test
运行测试时被编译。
每个测试都被定义为带有 #[test]
属性的函数。您还可以使用 #[should_panic]
属性检查测试是否会引发 panic。
由于我们处于智能合约的上下文中,设置 gas 限制非常重要。你可以通过使用 #[available_gas(X)]
属性来指定测试的 gas 限制。这也是确保合约功能保持在某个特定 gas 限制下的好方法!
注意:这里的 “gas” 一词指的是 Sierra gas,而不是 L1 的 gas
现在,让我们进入测试过程:
- 使用
deploy
函数的逻辑来声明和部署您的合约。 - 使用
assert
来验证合约在给定的上下文中的行为是否符合预期。
为了使测试更加方便,corelib 的 testing
模块提供了一些有用的函数:
set_caller_address(address: ContractAddress)
set_contract_address(address: ContractAddress)
set_block_number(block_number: u64)
set_block_timestamp(block_timestamp: u64)
set_account_contract_address(address: ContractAddress)
set_max_fee(fee: u128)
你可能还需要 corelib 中的 info
模块,它允许你访问有关当前交易上下文的信息:
get_caller_address() -> ContractAddress
get_contract_address() -> ContractAddress
get_block_info() -> Box<BlockInfo>
get_tx_info() -> Box<TxInfo>
get_block_timestamp() -> u64
get_block_number() -> u64
你可以在 Starknet Corelib 仓库 中找到完整的函数列表。 你还可以在 Cairo book 第8章 中找到有关在 cairo 中进行测试的详细说明。
Starknet Foundry
<!— TODO update this when Starknet Foundry is more mature. —>
Starknet Foundry是在Starknet上开发智能合约的强大工具包。它提供了对使用Forge
工具在 scarb
上测试Starknet智能合约的支持。
使用 snforge
进行测试与我们刚刚描述的过程类似,但更简化。此外,还有其他功能正在开发中,包括作弊码或并行测试执行。我们强烈推荐探索Starknet Foundry并将其纳入您你的项目中。
有关使用Starknet Foundry测试合约的更详细信息,请参阅Starknet Foundry Book - 合约测试。