安全考量
在开发软件时,确保其按预期运行通常比较简单而直接。然而,防止非预期的使用和漏洞可能更具挑战性。
在智能合约的开发中,安全性非常重要。仅仅一个简单的错误就可能导致宝贵资产的损失或某些功能的错误运行。
智能合约在一个公开的环境中执行,任何人都可以检查代码并与之交互。代码中的任何错误或漏洞都可能被恶意行为者利用。
本章介绍了编写安全智能合约的一般建议。在开发过程中,通过融入这些概念,你可以创建健壮可靠的智能合约。这将减少出现意外行为或漏洞的机会。
免责声明
本章并未提供所有可能的安全问题的详尽列表,也不能保证您的合约完全安全。
如果您正在开发用于实际生产环境中的智能合约,强烈建议由安全专家对其进行第三方审计。
安全思维
Cairo 是一种受到 Rust 启发的高度安全的语言。它的设计方式强制你覆盖所有可能的情况。在 Starknet 上的安全问题主要源于智能合约流程的设计,而非语言本身。
采用安全思维是编写安全智能合约的第一步。在编写代码时,尽量始终考虑所有可能的场景。
将智能合约视为有限状态机
智能合约中的交易是原子性的,这意味着它们要么成功,要么失败且不发生任何变化。
将智能合约视为状态机:它们有一组由构造函数约束定义的初始状态,而外部函数表示一组可能的状态转换。所谓交易也不过是一个状态转换。
assert
或 panic
函数可以用于在执行特定操作之前验证条件。您可以在无法恢复的错误与panic页面上了解更多信息。
这些验证会包括:
- 调用者提供的输入参数
- 执行要求
- 不变量(必须始终为真的条件)
- 其他函数调用的返回值
例如,您可以使用 assert
函数来验证用户是否具有足够的资金执行提款交易。如果条件不满足,交易将失败,并且合约的状态不会发生变化。
impl Contract of IContract<ContractState> {
fn withdraw(ref self: ContractState, amount: u256) {
let current_balance = self.balance.read();
assert(self.balance.read() >= amount, 'Insufficient funds');
self.balance.write(current_balance - amount);
}
使用这些函数来进行条件检查,添加一���有助于清晰地定义智能合约中每个函数可能的状态转换的边界的约束。这些检查确保合约的行为保持在预期范围内。
建议
检查-效果-交互 模式
检查-效果-交互模式是一种常见的设计模式,用于防止以太坊上的重入攻击。尽管在 Starknet 上更难实现重入攻击,但仍建议在智能合约中使用这种模式。
该模式由在函数中按照特定的操作顺序进行操作来实现:
- Checks(检查): 在执行任何状态更改之前,验证所有条件和输入。
- Effects(效果): 执行所有状态更改。
- Interactions(交互): 所有对其他合约的外部调用应在函数的最后进行。
访问控制
访问控制是限制对特定功能或资源的访问的过程。它是一种常见的安全机制,用于防止未经授权的敏感信息访问或操作。在智能合约中,某些函数可能经常需要被限制为特定的用户或角色使用。
您可以使用访问控制模式来轻松管理权限。该模式包括定义一组不同权限角色,并给不同用户分配对应的角色。每个函数都可限制为特定的角色才可访问。
#[starknet::contract]
mod access_control_contract {
use starknet::ContractAddress;
use starknet::get_caller_address;
trait IContract<TContractState> {
fn is_owner(self: @TContractState) -> bool;
fn is_role_a(self: @TContractState) -> bool;
fn only_owner(self: @TContractState);
fn only_role_a(self: @TContractState);
fn only_allowed(self: @TContractState);
fn set_role_a(ref self: TContractState, _target: ContractAddress, _active: bool);
fn role_a_action(ref self: ContractState);
fn allowed_action(ref self: ContractState);
}
#[storage]
struct Storage {
// Role 'owner': only one address
owner: ContractAddress,
// Role 'role_a': a set of addresses
role_a: LegacyMap::<ContractAddress, bool>
}
#[constructor]
fn constructor(ref self: ContractState) {
self.owner.write(get_caller_address());
}
// Guard functions to check roles
impl Contract of IContract<ContractState> {
#[inline(always)]
fn is_owner(self: @ContractState) -> bool {
self.owner.read() == get_caller_address()
}
#[inline(always)]
fn is_role_a(self: @ContractState) -> bool {
self.role_a.read(get_caller_address())
}
#[inline(always)]
fn only_owner(self: @ContractState) {
assert(Contract::is_owner(self), 'Not owner');
}
#[inline(always)]
fn only_role_a(self: @ContractState) {
assert(Contract::is_role_a(self), 'Not role A');
}
// You can easily combine guards to perform complex checks
fn only_allowed(self: @ContractState) {
assert(Contract::is_owner(self) || Contract::is_role_a(self), 'Not allowed');
}
// Functions to manage roles
fn set_role_a(ref self: ContractState, _target: ContractAddress, _active: bool) {
Contract::only_owner(@self);
self.role_a.write(_target, _active);
}
// You can now focus on the business logic of your contract
// and reduce the complexity of your code by using guard functions
fn role_a_action(ref self: ContractState) {
Contract::only_role_a(@self);
// ...
}
fn allowed_action(ref self: ContractState) {
Contract::only_allowed(@self);
// ...
}
}
}
静态分析工具
静态分析指的是在不执行代码的情况下对其进行检查的过程,重点关注其结构、语法和属性。它涉及到分析源代码,以识别潜在的问题、漏洞或违反特定规则的行为。
通过定义规则,例如编码规范或安全指南,开发人员可以使用静态分析工具自动检查代码是否符合这些标准。
参考文献: