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 攻击.