Move 安全指南
Move 语言天生具备安全性,内置了类型系统和线性逻辑等多种特性.尽管如此,由于其新颖性以及部分业务逻辑的复杂性,开发者未必总能熟悉 Move 的安全编码模式,进而导致漏洞.
本指南旨在弥补这一空白,详细介绍常见的反模式及其安全替代方案.通过实际示例说明安全问题的产生方式,并推荐安全编码最佳实践.希望本指南能帮助开发者加深对 Move 安全机制的理解,确保智能合约的健壮开发.
对象所有权检查
Section titled “对象所有权检查”每个 Object<T>
都可以被任何人访问,这意味着任何人都可以将 Object<T>
传递给任意函数,即使调用者并不拥有该对象.
因此,务必验证 signer
是否为对象的真正所有者.
不安全示例代码
Section titled “不安全示例代码”在此模块中,用户需先购买订阅才能执行某些操作.用户调用注册函数以获取 Object<Subscription>
,之后可用其执行操作.
module 0x42::example {
struct Subscription has key { end_subscription: u64 }
entry fun registration(user: &signer, end_subscription: u64) { let price = calculate_subscription_price(end_subscription); payment(user,price);
let user_address = address_of(user); let constructor_ref = object::create_object(user_address); let subscription_signer = object::generate_signer(&constructor_ref); move_to(&subscription_signer, Subscription { end_subscription }); }
entry fun execute_action_with_valid_subscription( user: &signer, obj: Object<Subscription> ) acquires Subscription { let object_address = object::object_address(&obj); let subscription = borrow_global<Subscription>(object_address); assert!(subscription.end_subscription >= aptos_framework::timestamp::now_seconds(),1); // Use the subscription [...] }}
在此不安全示例中,execute_action_with_valid_subscription
未验证用户是否拥有传入的 obj
.因此,任何人都可以使用他人的订阅,绕过付费要求.
安全示例代码
Section titled “安全示例代码”确保 signer 拥有该对象.
module 0x42::example {
struct Subscription has key { end_subscription: u64 }
entry fun registration(user: &signer, end_subscription: u64) { let price = calculate_subscription_price(end_subscription); payment(user,price);
let user_address = address_of(user); let constructor_ref = object::create_object(user_address); let subscription_signer = object::generate_signer(&constructor_ref); move_to(&subscription_signer, Subscription { end_subscription }); }
entry fun execute_action_with_valid_subscription( user: &signer, obj: Object<Subscription> ) acquires Subscription { //ensure that the signer owns the object. assert!(object::owner(&obj)==address_of(user),ENOT_OWNWER); let object_address = object::object_address(&obj); let subscription = borrow_global<Subscription>(object_address); assert!(subscription.end_subscription >= aptos_framework::timestamp::now_seconds(),1); // Use the subscription [...] }}
全局存储访问控制
Section titled “全局存储访问控制”仅接受 &signer
作为参数并不足以实现访问控制.务必断言 signer 是否为预期账户,尤其是在执行敏感操作时.
如果未做授权校验,任何用户都可执行特权操作.
不安全示例代码
Section titled “不安全示例代码”此代码片段允许任何调用 delete
函数的用户删除 Object
,未校验调用者权限.
module 0x42::example { struct Object has key{ data: vector<u8> }
public fun delete(user: &signer, obj: Object) { let Object { data } = obj; }}
安全示例代码
Section titled “安全示例代码”更好的做法是利用 Move 提供的全局存储,直接通过 signer::address_of(signer)
访问数据.这样可确保只操作 signer 拥有的数据,极大降低访问控制出错风险.
module 0x42::example { struct Object has key{ data: vector<u8> }
public fun delete(user: &signer) { let Object { data } = move_from<Object>(signer::address_of(user)); }}
遵循最小权限原则:
- 始终以私有函数为起点,按业务需求逐步提升可见性.
- 仅为 Aptos CLI 或 SDK 使用的函数加
entry
. - 仅允许特定模块访问的函数使用
friend
. - 仅读取存储,不修改状态的函数使用
#[view]
装饰器.注意,#[view]
函数可被间接调用,此时可能会修改存储.
函数可见性决定了谁可以调用该函数,是强制访问控制的重要手段,对智能合约安全至关重要:
- private 函数仅能在定义它的模块内调用,外部模块及 CLI/SDK 无法访问,防止合约内部被意外调用.
module 0x42::example { fun sample_function() { /* ... */ }}
public(friend)
允许指定 friends 模块调用,实现合约间受控交互,同时限制一般访问.
module 0x42::example { friend package::mod;
public(friend) fun sample_function() { /* ... */ }}
public
函数可被任意已发布模块或脚本调用.
module 0x42::example { public fun sample_function() { /* ... */ }}
#[view]
装饰的函数不能修改存储,仅用于安全读取信息.
module 0x42::example { #[view] public fun read_only() { /* ... */ }}
- Move 中的
entry
修饰符用于标记交易入口点.带有entry
的函数作为区块链交易的执行起点.
module 0x42::example { entry fun f(){}}
总结如下:
模块自身 | 其他模块 | Aptos CLI/SDK | |
---|---|---|---|
private | ✅ | ⛔ | ⛔ |
public(friend) | ✅ | ✅ if friend ⛔ otherwise | ⛔ |
public | ✅ | ✅ | ⛔ |
entry | ✅ | ⛔ | ✅ |
这种分层可见性确保只有授权实体能执行特定函数,大大降低因过度暴露导致的漏洞或攻击风险.
注意,可将 entry
与 public
或 public(friend)
组合使用:
module 0x42::example { public(friend) entry fun sample_function() { /* ... */ }}
此时 sample_function
可被 Aptos CLI/SDK 及所有声明为 friend 的模块调用.
遵循最小权限原则可防止函数过度暴露,将访问范围限制在业务所需范围内.
类型与数据结构
Section titled “类型与数据结构”泛型类型检查
Section titled “泛型类型检查”泛型可用于定义适用于不同输入数据类型的函数和结构体.使用泛型时,务必确保泛型类型有效且符合预期.详细了解 泛型.
未检查的泛型可能导致未授权操作或交易中止,危及协议完整性.
不安全示例代码
Section titled “不安全示例代码”下方代码为简化版闪电贷.
在 flash_loan<T>
函数中,用户可借出指定类型 T
的币及记录应还金额(含手续费)的 Receipt
.
repay_flash_loan<T>
接收 Receipt
和 Coin<T>
,仅断言还款币值大于等于 Receipt
,但未校验还款币种是否与借出币种一致,允许用价值较低的币还款.
module 0x42::example { struct Coin<T> { amount: u64 }
struct Receipt { amount: u64 }
public fun flash_loan<T>(user: &signer, amount: u64): (Coin<T>, Receipt) { let (coin, fee) = withdraw(user, amount); ( coin, Receipt {amount: amount + fee} ) }
public fun repay_flash_loan<T>(rec: Receipt, coins: Coin<T>) { let Receipt{ amount } = rec; assert!(coin::value<T>(&coin) >= rec.amount, 0); deposit(coin); }}
安全示例代码
Section titled “安全示例代码”Aptos Framework 示例中,创建了由泛型 K
和 V
组成的键值表.相关 add
函数参数为 Table<K, V>
,key
和 value
,类型分别为 K
和 V
.phantom
语法确保 key 和 value 类型与表一致,防止类型不匹配.详细了解 phantom
泛型参数.
module 0x42::example { struct Table<phantom K: copy + drop, phantom V> has store { handle: address, }
public fun add<K: copy + drop, V>(table: &mut Table<K, V>, key: K, val: V) { add_box<K, V, Box<V>>(table, key, Box { val }) }}
得益于 Move 语言的类型检查机制,我们可优化闪电贷协议代码.如下代码确保 repay_flash_loan
传入的币与借出币一致.
module 0x42::example { struct Coin<T> { amount: u64 } struct Receipt<phantom T> { amount: u64 }
public fun flash_loan<T>(_user: &signer, amount:u64): (Coin<T>, Receipt<T>) { let (coin, fee) = withdraw(user, amount); (coin,Receipt { amount: amount + fee}) }
public fun repay_flash_loan<T>(rec: Receipt<T>, coins: Coin<T>) { let Receipt{ amount } = rec; assert!(coin::value<T>(&coin) >= rec.amount, 0); deposit(coin); }}
资源管理与无界执行
Section titled “资源管理与无界执行”高效的资源管理与无界执行预防对协议安全和 gas 效率至关重要,设计合约时需重点考虑:
- 避免对允许无限添加条目的公开结构体进行遍历.
- 用户资产(如币,NFT)应存储在各自账户下.
- 模块或包相关信息应存储于 Object,与用户数据分离.
- 用户操作应分散存储,避免全部集中于全局空间.
忽视上述原则,攻击者可消耗大量 gas 并导致交易中止,进而阻断应用功能.
不安全示例代码
Section titled “不安全示例代码”下方代码遍历所有订单,攻击者可通过注册大量订单阻塞操作:
module 0x42::example { public fun get_order_by_id(order_id: u64): Option<Order> acquires OrderStore { let order_store = borrow_global_mut<OrderStore>(@admin); let i = 0; let len = vector::length(&order_store.orders); while (i < len) { let order = vector::borrow<Order>(&order_store.orders, i); if (order.id == order_id) { return option::some(*order) }; i = i + 1; }; return option::none<Order>() } //O(1) in time and gas operation. public entry fun create_order(buyer: &signer) { /* ... */ }}
安全示例代码
Section titled “安全示例代码”推荐将订单管理系统设计为每个用户的订单存储在其账户下,而非全局订单池.这样既能隔离用户数据提升安全性,也能分散数据负载提升可扩展性.应通过用户账户访问订单,而非 borrow_global_mut<OrderStore>(@admin)
.
module 0x42::example { public fun get_order_by_id(user: &signer, order_id: u64): Option<Order> acquires OrderStore { let order_store = borrow_global_mut<OrderStore>(signer::address_of(user)); if (smart_table::contains(&order_store.orders, order_id)) { let order = smart_table::borrow(&order_store.orders, order_id); option::some(*order) } else { option::none<Order>() } }}
同时建议根据操作需求选用高效数据结构,如 SmartTable
.
Move 能力(Abilities)
Section titled “Move 能力(Abilities)”Move 的能力是一组权限,用于控制数据结构的操作.开发者需谨慎分配能力,仅在必要时赋予,并理解其安全影响,防止安全漏洞.
能力 | 描述 |
---|---|
copy | 允许值被复制,可在合约内多次使用. |
drop | 允许值被丢弃,防止资源泄漏. |
store | 允许数据存储于全局存储,实现跨交易持久化. |
key | 允许数据作为全局存储的 key,便于数据检索与操作. |
详细了解 能力.
能力使用不当可能导致安全问题,如敏感数据被复制(copy
),资源泄漏(drop
),全局存储误用(store
).
不安全示例代码
Section titled “不安全示例代码”module 0x42::example { struct Token has copy { } struct FlashLoan has drop { }}
Token
拥有copy
能力,允许复制,可能导致双花和通胀,币值贬损.FlashLoan
拥有drop
能力,允许借款人销毁贷款,逃避还款.
算术运算因向下取整导致精度损失,可能使协议低估计算结果.
Move 支持六种无符号整数类型:u8
,u16
,u32
,u64
,u128
,u256
.Move 中的除法操作会截断小数部分,向下取整,可能导致协议低估结果.
精度误差可能带来广泛影响,如财务失衡,数据不准确,决策失误,甚至造成损失或安全风险.准确计算对系统可靠性和用户信心至关重要.
不安全示例代码
Section titled “不安全示例代码”module 0x42::example { public fun calculate_protocol_fees(size: u64): (u64) { return size * PROTOCOL_FEE_BPS / 10000 }}
若 size
小于 10000 / PROTOCOL_FEE_BPS
,则手续费将被向下取整为 0,用户可零手续费与协议交互.
安全示例代码
Section titled “安全示例代码”以下为两种缓解方案:
- 设置最小订单规模阈值,大于
10000 / PROTOCOL_FEE_BPS
,确保手续费不为零.
module 0x42::example { const MIN_ORDER_SIZE: u64 = 10000 / PROTOCOL_FEE_BPS + 1;
public fun calculate_protocol_fees(size: u64): (u64) { assert!(size >= MIN_ORDER_SIZE, 0); return size * PROTOCOL_FEE_BPS / 10000 }}
- 检查手续费非零,若为零则设定最小手续费或拒绝交易.
module 0x42::example { public fun calculate_protocol_fees(size: u64): (u64) { let fee = size * PROTOCOL_FEE_BPS / 10000; assert!(fee > 0, 0); return fee; }}
整数运算注意事项
Section titled “整数运算注意事项”Move 中,整数运算具备安全性,防止溢出和下溢,避免异常行为或漏洞:
- 加法(
+
)和乘法(*
)若结果超出类型范围会直接中止程序. - 减法(
-
)若结果小于零会中止. - 除法(
/
)若除数为零会中止. - 左移(
<<
)不会因溢出中止,超出位数会产生错误值或不可预测行为.
详细了解 运算.
不当运算可能导致合约执行异常或数据错误.
Aptos Objects
Section titled “Aptos Objects”ConstructorRef 泄漏
Section titled “ConstructorRef 泄漏”创建对象时,切勿暴露对象的 ConstructorRef
,否则可被用于向对象添加资源.ConstructorRef
还能生成其他能力(或 “Refs”),用于更改或转移对象所有权.详细了解 对象能力.
漏洞示例代码
Section titled “漏洞示例代码”例如,若 mint
函数返回 NFT 的 ConstructorRef
,可被转换为 TransferRef
并存储于全局,允许原持有者在 NFT 售出后转回.
module 0x42::example { use std::string::utf8;
public fun mint(creator: &signer): ConstructorRef { let constructor_ref = token::create_named_token( creator, string::utf8(b"Collection Name"), string::utf8(b"Collection Description"), string::utf8(b"Token"), option::none(), string::utf8(b"https://mycollection/token.jpeg"), ); constructor_ref }}
安全示例代码
Section titled “安全示例代码”mint
函数不返回 CostructorRef
:
module 0x42::example { use std::string::utf8;
public fun mint(creator: &signer) { let constructor_ref = token::create_named_token( creator, string::utf8(b"Collection Name"), string::utf8(b"Collection Description"), string::utf8(b"Token"), option::none(), string::utf8(b"https://mycollection/token.jpeg"), ); }}
Object 账户
Section titled “Object 账户”在 Aptos Framework 中,多个具备 key
能力的资源可存储于同一 object 账户.
但对象应隔离存储于不同账户,否则对一个对象的修改会影响整个集合.
例如,转移一个资源会导致所有组成员一同转移,因为 transfer 操作针对 ObjectCore
,即账户下所有资源的通用标签.
详细了解 Aptos Objects.
不安全示例代码
Section titled “不安全示例代码”mint_two
允许 sender
为自己创建 Monkey
并向 recipient
发送 Toad
.
由于 Monkey
和 Toad
属于同一 object 账户,结果是两者都归 recipient
所有.
module 0x42::example { #[resource_group(scope = global)] struct ObjectGroup { }
#[resource_group_member(group = 0x42::example::ObjectGroup)] struct Monkey has store, key { }
#[resource_group_member(group = 0x42::example::ObjectGroup)] struct Toad has store, key { }
fun mint_two(sender: &signer, recipient: &signer) { let constructor_ref = &object::create_object_from_account(sender); let sender_object_signer = object::generate_signer(constructor_ref); let sender_object_addr = object::address_from_constructor_ref(constructor_ref);
move_to(sender_object_signer, Monkey{}); move_to(sender_object_signer, Toad{}); let monkey_object: Object<Monkey> = object::address_to_object<Monkey>(sender_object_addr); object::transfer<Monkey>(sender, monkey_object, signer::address_of(recipient)); }}
安全示例代码
Section titled “安全示例代码”应将对象分别存储于不同 object 账户:
module 0x42::example { #[resource_group(scope = global)] struct ObjectGroup { }
#[resource_group_member(group = 0x42::example::ObjectGroup)] struct Monkey has store, key { }
#[resource_group_member(group = 0x42::example::ObjectGroup)] struct Toad has store, key { }
fun mint_two(sender: &signer, recipient: &signer) { let sender_address = signer::address_of(sender);
let constructor_ref_monkey = &object::create_object(sender_address); let constructor_ref_toad = &object::create_object(sender_address); let object_signer_monkey = object::generate_signer(&constructor_ref_monkey); let object_signer_toad = object::generate_signer(&constructor_ref_toad);
move_to(object_signer_monkey, Monkey{}); move_to(object_signer_toad, Toad{});
let object_address_monkey = signer::address_of(&object_signer_monkey);
let monkey_object: Object<Monkey> = object::address_to_object<Monkey>(object_address_monkey); object::transfer<Monkey>(sender, monkey_object, signer::address_of(recipient)); }}
抢先交易(Front-running)
Section titled “抢先交易(Front-running)”抢先交易是指利用对未来已提交操作的预知,提前执行交易以获利.这种行为会破坏公平性,使抢先者获得不正当优势.
抢先交易会破坏 DApp 的公平性和完整性,导致资金损失,游戏不公,市场价格操纵,甚至平台信任丧失.
不安全示例代码
Section titled “不安全示例代码”以彩票为例,用户选择 1~100 的数字参与.管理员调用 set_winner_number
设置中奖号码,随后通过 evaluate_bets_and_determine_winners
评奖.
抢先者可在 set_winner_number
后观察中奖号码,提交或修改投注以匹配中奖号码,在 evaluate_bets_and_determine_winners
执行前获利.
module 0x42::example { struct LotteryInfo { winning_number: u8, is_winner_set: bool, }
struct Bets { }
public fun set_winning_number(admin: &signer, winning_number: u8) { assert!(signer::address_of(admin) == @admin, 0); assert!(winning_number < 10,0); let lottery_info = LotteryInfo { winning_number, is_winner_set: true }; move_to<LotteryInfo>(admin, lottery_info); }
public fun evaluate_bets_and_determine_winners(admin: &signer) acquires LotteryInfo, Bets { assert!(signer::address_of(admin) == @admin, 0); let lottery_info = borrow_global<LotteryInfo>(admin); assert(lottery_info.is_winner_set, 1);
let bets = borrow_global<Bets>(admin); let winners: vector<address> = vector::empty();
let winning_bets_option = smart_table::borrow_with_default(&bets.bets, lottery_info.winning_number, &vector::empty());
vector::iter(winning_bets_option, |bet| { vector::push_back(&mut winners, bet.player); }); distribute_rewards(&winners); }}
安全示例代码
Section titled “安全示例代码”可通过实现 finalize_lottery
,在单笔交易中公布答案并结束游戏,并将其他函数设为私有.这样一旦答案公布,系统即不再接受新答案,杜绝抢先交易.
module 0x42::example { public fun finalize_lottery(admin: &signer, winning_number: u64) { set_winning_number(admin, winning_number); evaluate_bets_and_determine_winners(admin); }
fun set_winning_number(admin: &signer, winning_number: u64) { }
fun evaluate_bets_and_determine_winners(admin: &signer) acquires LotteryInfo, Bets { }}
价格预言机操纵
Section titled “价格预言机操纵”在 Defi 应用中,若价格预言机通过流动性池中代币比例定价,易被大户操纵.大户可通过调整持仓影响流动性比例,进而操纵价格,甚至耗尽资金池.
建议采用多预言机定价.
安全示例代码
Section titled “安全示例代码”Thala 采用分层预言机设计,主备双预言机,具备智能切换逻辑,能应对对抗性场景,始终提供高精度价格.详细了解.
Token 标识符冲突
Section titled “Token 标识符冲突”处理 Token 时,确保用于比较 Token 结构体以确定顺序的方法不会导致冲突.将地址,模块,结构体名拼接为 vector 不足以区分同名但应唯一的 Token.
否则协议可能因冲突错误拒绝合法交易对,危及资金安全.
不安全示例代码
Section titled “不安全示例代码”get_pool_address
为交易对生成唯一池地址.其通过拼接符号生成种子,但用户可自定义 Object<Metadata>
符号,可能伪造已存在实例,导致种子冲突.
module 0x42::example { public fun get_pool_address(token_1: Object<Metadata>, token_2: Object<Metadata>): address { let token_symbol = string::utf8(b"LP-"); string::append(&mut token_symbol, fungible_asset::symbol(token_1)); string::append_utf8(&mut token_symbol, b"-"); string::append(&mut token_symbol, fungible_asset::symbol(token_2)); let seed = *string::bytes(&token_symbol); object::create_object_address(&@swap, seed) }}
安全示例代码
Section titled “安全示例代码”object::object_address
为每个 Object<Metadata>
返回唯一标识符.
module 0x42::example { public fun get_pool_address(token_1: Object<Metadata>, token_2: Object<Metadata>): address { let seeds = vector[]; vector::append(&mut seeds, bcs::to_bytes(&object::object_address(&token_1))); vector::append(&mut seeds, bcs::to_bytes(&object::object_address(&token_2))); object::create_object_address(&@swap, seed) }}
协议应具备高效暂停操作的能力.不可升级协议需内置暂停功能;可升级协议可通过合约或升级实现暂停.团队应具备自动化工具以快速执行.
缺乏暂停机制会导致漏洞暴露时间过长,带来重大损失.高效暂停功能可及时应对安全威胁,漏洞等,保障用户资产和协议安全.
安全示例代码
Section titled “安全示例代码”集成暂停功能示例:
module 0x42::example { struct State { is_paused: bool, }
public entry fun pause_protocol(admin: &signer) { assert!(signer::address_of(admin)==@protocol_address, ERR_NOT_ADMIN); let state = borrow_global_mut<State>(@protocol_address); state.is_paused = true; }
public entry fun resume_protocol(admin: &signer) { assert!(signer::address_of(admin)==@protocol_address, ERR_NOT_ADMIN); let state = borrow_global_mut<State>(@protocol_address); state.is_paused = false; }
public fun main(user: &signer) { let state = borrow_global<State>(@protocol_address); assert!(!state.is_paused, 0); // ... }}
智能合约发布密钥管理
Section titled “智能合约发布密钥管理”测试网和主网共用账户存在安全风险.测试网私钥常存储于不安全环境(如笔记本),易泄露.攻击者若获取测试网私钥,可升级主网合约.
关于随机性及其防止可预测性的作用,详见:随机性指南.
随机性 - test-and-abort
Section titled “随机性 - test-and-abort”Aptos 始终以安全为先.编译时会确保无 randomness API 被 public 函数调用.但用户可通过
#[lint::allow_unsafe_randomness]
允许 public 函数调用.
若 public
函数直接或间接调用 randomness API,恶意用户可利用函数可组合性,在结果不理想时中止交易,反复尝试直至获利,破坏随机性.
漏洞示例代码
Section titled “漏洞示例代码”module user::lottery { fun mint_to_user(user: &signer) { move_to(user, WIN {}); }
#[lint::allow_unsafe_randomness] public entry fun play(user: &signer) { let random_value = aptos_framework::randomness::u64_range(0, 100); if (random_value == 42) { mint_to_user(user); } }}
此例中,play
为 public
,可被其他模块组合.攻击者可调用后检查是否中奖,未中奖则中止并重试.
module attacker::exploit { entry fun exploit(attacker: &signer) { @user::lottery::play(attacker); assert!(exists<@user::lottery::WIN>(address_of(attacker))); }}
解决方法:所有调用 randomness API 的函数(直接或间接)应设为 entry
,而非 public
或 public entry
.
安全示例代码
Section titled “安全示例代码”module user::lottery { fun mint_to_user(user: &signer) { move_to(user, WIN {}); }
#[lint::allow_unsafe_randomness] entry fun play(user: &signer) { let random_value = aptos_framework::randomness::u64_range(0, 100); if (random_value == 42) { mint_to_user(user); } }}
随机性 - undergasing
Section titled “随机性 - undergasing”若函数不同分支消耗 gas 不同,攻击者可通过设置 gas 上限偏向结果.如下例,不同路径消耗 gas 不同.
漏洞示例代码
Section titled “漏洞示例代码”module user::lottery {
//transfer 10 aptos from admin to user fun win(user: &signer) { let admin_signer = &get_admin_signer(); let aptos_metadata = get_aptos_metadata(); primary_fungible_store::transfer(admin_signer, aptos_metadata, address_of(user),10); }
//transfer 10 aptos from user to admin, then 1 aptos from admin to fee_admin fun lose(user: &signer) {
//user to admin let aptos_metadata = get_aptos_metadata(); primary_fungible_store::transfer(user, aptos_metadata, @admin, 10);
//admin to fee_admin let admin_signer = &get_admin_signer(); primary_fungible_store::transfer(admin_signer, aptos_metadata, @fee_admin, 1); }
#[randomness] entry fun play(user: &signer) { let random_value = aptos_framework::randomness::u64_range(0, 100); if (random_value == 42) { win(user); } else { lose(user); } }}
此彩票示例中,win
和 lose
消耗 gas 不同.
lose
消耗更多 gas.攻击者可设置 gas 上限,仅足够 win
,不足以执行 lose
,从而确保永远不会走 lose
分支.攻击者可反复调用直至中奖.
安全示例代码
Section titled “安全示例代码”可通过以下方式防护:
- 保证更优结果消耗的 gas 不少于较差结果.
- 仅允许管理员调用 randomness API.
- 保证 entry 函数无论随机结果如何都能正常执行.可先提交随机结果,再用随机结果在另一交易中执行操作,避免立即基于随机性分支,确保 gas 消耗一致.
未来将提供更多功能,支持更复杂代码安全防护 undergasing 攻击.