本文介绍了Cairo中如何使用assert宏实现访问控制,对比了Solidity中的modifier,展示了如何在Cairo中使用函数和assert或assert!宏来限制函数访问权限,并详细讲解了 assert 和 assert! 宏的区别、使用方法和类型限制,通过示例和练习,帮助读者理解在Cairo中实现访问控制的方法。
访问控制定义了谁可以调用特定的函数或修改合约的行为。本文解释了 Cairo 如何使用 assert 宏来实现访问控制。
在 Solidity 中,modifiers 是一种围绕函数封装行为的简洁方式。它们通常用于访问控制。考虑以下合约定义了一个 onlyOwner modifier,它确保只有合约所有者可以调用 callMe 函数:
// SPDX-License-Identifier: MIT
pragma solidity =0.8.30;
contract SomeContract {
address owner;
constructor() {
owner = msg.sender;
}
// THE `ONLYOWNER` MODIFIER
modifier onlyOwner() {
require(msg.sender == owner, "Not the owner");
_;
}
function callMe() public onlyOwner {
// callMe logic
}
}
Modifiers 允许你通过将前提条件移动到其他地方,例如我们上面的 onlyOwner modifier 中,来保持你的主函数逻辑清晰。
在 Cairo 中,没有 modifier 关键字。相反,我们定义一个常规函数来强制执行我们的检查,比如 only_owner,并在 call_me 函数内部调用它。
下面的代码展示了它可能看起来像什么的一个例子:
#[starknet::contract]
mod SomeContract {
// import the required functions from the starknet core library
use starknet::ContractAddress;
use starknet::get_caller_address;
use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess};
#[storage]
struct Storage {
owner: ContractAddress,
}
#[constructor]
fn constructor(ref self: ContractState) {
self.owner.write(get_caller_address());
}
#[generate_trait]
impl Internal of InternalTrait {
fn only_owner(self: @ContractState) {
let caller = get_caller_address();
let stored_owner = self.owner.read();
// ENSURES THE CALLER IS THE OWNER OR REVERT
assert(caller == stored_owner, 'Not owner');
}
}
#[abi(embed_v0)]
impl SomeContractImpl of super::ISomeContract<ContractState> {
// CALL_ME FUNCTION
fn call_me(ref self: ContractState) {
self.only_owner();
// callMe logic
}
}
}
这个 Cairo 版本通过限制对 call_me 函数的访问来镜像 Solidity 模式。它通过断言调用者的地址与合约状态中存储的所有者匹配来确保只有所有者可以调用它。
assert(caller == stored_owner, 'Not owner');
assert 函数的行为类似于 Solidity 的 require,如果条件失败,它会停止执行并回滚事务。为了使其更好,Cairo 提供了另一个名为 assert! 的函数,它支持格式化的错误消息,使其更具表现力。
assert Vs assert!虽然 assert 函数和 assert! 宏(! 将宏与函数区分开来)具有相同的目的,即确保条件为真,但它们在报告错误的方式上有所不同。
assert:
第一个参数 condition 是一个布尔表达式。如果它是 false,程序会 用单引号 中的固定错误消息 panic。
assert(condition, 'static error message');
assert!:
condition 是一个布尔表达式。如果它是 false,程序会 panic,并显示第二个参数。assert!(condition, "Formatted error: {}", variable);
{} 在格式化字符串中的含义在格式化字符串中,{} 是一个占位符。当代码运行时,variable 的值会被转换为字符串,并插入到 {} 出现的位置。
可以把它想象成一个 填空题:
let name = "Alice";
println!("Hello, {}", name);
// Prints: Hello, Alice
我们可以有多个占位符:
println!("x = {}, y = {}", x, y);
顺序很重要:每个 {} 都由字符串后面的相应参数填充。
这为开发者在调试或处理错误时提供了更大的灵活性。你可以包含消息中的运行时值,而不是静态字符串,这是 Solidity 的 require 不直接支持的。
推荐的方法是使用
assert!,即使在生产环境中也是如此。
assert! 中支持的类型并非所有类型都可以在 assert! 消息中使用。只有实现了 core::fmt::Display trait 的类型才能在 assert! 消息格式中使用。Display trait 定义了在使用 {} 格式说明符时,类型如何转换为字符串表示形式。这些是:
ByteArrayboolNonZero<T> (对于任何本身实现 Display 的 T)felt252, u8, u16, u32, u64, u128, u256,以及如果存在的有符号变体)@T (上面任何 Display 类型的引用)例如,像 felt252 这样的类型是可以的,但是自定义结构体或像 ContractAddress 这样的类型会引发错误,因为它们没有实现 Display trait。
如果你尝试这样做:
let caller: ContractAddress = get_caller_address();
// ❌ This will fail to compile
assert!(caller == owner, "Caller was: {}", caller);
你会看到一个像这样的错误:
Trait has no implementation in context: core::fmt::Display::<core::starknet::contract_address::ContractAddress>
为了解决这个问题,如果你只需要数字表示,你可以将地址转换为 felt252:
let caller: ContractAddress = get_caller_address();
let caller_felt: felt252 = caller.into();
// ✅ This works, assuming the `owner` variable is of type felt252 too
assert!(caller_felt == owner, "Caller was: {}", caller_felt);
因此,虽然 assert! 为你提供了富有表现力的错误处理,但请记住格式化消息时的类型要求。
练习: 编写一个 Cairo 函数,该函数接受两个数字 n 和 d,并返回它们的除法。如果 d 为零,该函数应回滚并显示消息:“n 不能被 d 整除”(包括错误中 n 和 d 的实际值)。提示:使用 assert! 函数。要解决 safe_divide 练习,请克隆此 repo。
本文是关于 Starknet 上的 Cairo 编程 的系列教程的一部分
- 原文链接: rareskills.io/post/cairo...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!