系统对象:TxContext 和避免安全漏洞

另一个非常常用的系统对象是 TxContext。我们已经看到它在两个主要用例中的应用:

  1. 使用 object::new 创建一个新对象的 id
  2. 使用 tx_context::sender 获取发送者
public struct MyObject has key {
    id: UID,
    value_1: u64,
    value_2: u64,
}

public fun create_object(value_1: u64, value_2: u64, ctx: &mut TxContext) {
    let object = MyObject {
        id: object::new(ctx),
        value_1,
        value_2,
    };
    transfer::transfer(object, tx_context::sender(ctx));
}

开发人员应该密切关注如何使用从 tx_context::sender 返回的发送者地址。 将新创建的对象发送到该地址是可以的,但将其用作认证或用户直接调用此函数的意图证明可能会有问题。例如:

module 0x123::safe_module {
    public fun claim_rewards(amount: u64, receiver: address, ctx: &mut TxContext) {
        let sender = tx_context::sender(ctx);
        assert!(sender == @0x12345, ENOT_AUTHORIZED);
        // 发送定额奖励到接收者地址
    }
}

在上面的示例中,我们希望编写一个特殊函数 claim_rewards,允许特定地址调用并提取一定数量的奖励金。 表面上看起来是安全的,但可能会被利用!恶意开发者可以编写一个模块,向用户承诺空投,并在代码中执行以下操作:

module 0x123::malicious_module {
    const MALICIOUS_DEVELOPER: address = @0x98765;

    public fun airdrop(ctx: &mut TxContext) {
        safe_module::claim_rewards(1000, MALICIOUS_DEVELOPER, ctx);
    }
}

这样会立即耗尽用户的奖励!safe_module 无法轻易区分 ctx 对象是由 VM(首次函数调用)传递的,还是由其他对象的另一个函数传递的。 一个解决方案是将 claim_rewards 设置为入口函数,这样它必须由用户直接调用。 然而,在某些情况下,如果我们希望自己的代码(来自同一包中的不同模块)调用此函数,这可能并不理想。

总的来说,使用 tx_context::sender 作为认证机制是有风险的,如果可能存在任何利用的可能性,应进行非常彻底的评估。

tx_context 模块提供的其他功能包括:

  • digest:返回交易哈希
  • epochepoch_timestamp_ms:返回当前的 epoch 编号及相应的时间戳
  • fresh_object_address:使用与 object::new 相同的底层函数生成新对象的地址