跳转至

神奇的时钟

介绍

Sui提供一个共享的Clock对象,地址为0x6。它是一个系统对象,会在一定时间更新数据,用来存储自Unix纪元以来的当前时间(以毫秒为单位)。

以下为Clock在源码中的定义:

/// Singleton shared object that exposes time to Move calls. This
/// object is found at address 0x6, and can only be read (accessed
/// via an immutable reference) by entry functions.
///
/// Entry Functions that attempt to accept `Clock` by mutable
/// reference or value will fail to verify, and honest validators
/// will not sign or execute transactions that use `Clock` as an
/// input parameter, unless it is passed by immutable reference.
public struct Clock has key {
    id: UID,
    /// The clock's timestamp, which is set automatically by a
    /// system transaction every time consensus commits a
    /// schedule, or by `sui::clock::increment_for_testing` during
    /// testing.
    timestamp_ms: u64,
}

例题

接下来,考虑这么一个问题:

module clock::clock;

use sui::clock::Clock;
use sui::event;

const ENoAttempts: u64 = 0;

public struct Challenge has key {
    id: UID,
    count: u8,
    target: u64
}

public struct Flag has copy, drop {
    owner: address,
    success: bool
}

fun init(ctx: &mut TxContext) {
    transfer::share_object(Challenge {
        id: object::new(ctx),
        count: 1,
        target: ctx.epoch()
    });
}

public fun check_timestamp(challenge: &Challenge, timestamp: u64): bool {
    timestamp % 998244353 == challenge.target
}

entry fun get_flag(challenge: &mut Challenge, clock: &Clock, ctx: &TxContext) {
    assert!(challenge.count > 0, ENoAttempts);
    challenge.count = 0;
    event::emit(Flag {
        owner: ctx.sender(),
        success: check_timestamp(challenge, clock.timestamp_ms())
    });
}

只有一次机会,唯有Clock对象中存储的时间戳对998244353取模后恰好为challenge.target才可夺旗有效。那么,我们该如何控制Clock?这是一个系统对象,想要人为干预控制并不现实,但是,我们可以从另一个角度来考虑解题。

区块链浏览器上不难发现,Clock对象也是通过一次次调用来更新其中存储的值的。在Sui上,也并非所有交易都可以并行,尤其是当遇到同一个对象的时候。所以,我们可以额外编写一个判断的合约:

module solve::solve;

use sui::clock::Clock;

use clock::clock::{Challenge, check_timestamp};

const ENotCorrectTimestamp: u64 = 0;

entry fun solve(challenge: &Challenge, clock: &Clock) {
    assert!(check_timestamp(challenge, clock.timestamp_ms()), ENotCorrectTimestamp);
}

solveget_flag放在同一个PTB中组合成一笔交易,在这笔交易中,Clock对象的值将不发生变化。如果无法通过solve中的断言,那么整笔交易失败,并不会浪费challenge中的尝试次数;而一旦断言成功,即可一举夺旗。

拓展

Epoch

TxContext(交易上下文,必须是函数最后一个参数,由系统创建,包含该笔交易相关信息)中,可以通过ctx.epoch()读取到epoch:表示当前系统的运行周期,大约每24小时更改一次;也可以通过ctx.epoch_timestamp_ms()读取到当前周期开始时对应的时间戳。

通常,epoch用于质押和系统操作,也可以用于模拟24小时周期等。

PTB

可编程交易块,允许用户在单笔交易中调用多个Move函数,是生成交易的轻量而灵活的方式。在PTB中,每个函数调用都按照顺序执行,甚至可以在后一个函数中使用前一个函数的返回值,一整笔交易是一个原子操作,即:其中任一调用失败,整笔交易失败,不会更改任何链上数据,唯一的损失可能就是需要支付一定量的gas费。