神奇的时钟¶
介绍¶
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);
}
将solve
和get_flag
放在同一个PTB
中组合成一笔交易,在这笔交易中,Clock
对象的值将不发生变化。如果无法通过solve
中的断言,那么整笔交易失败,并不会浪费challenge
中的尝试次数;而一旦断言成功,即可一举夺旗。
拓展¶
Epoch¶
在TxContext
(交易上下文,必须是函数最后一个参数,由系统创建,包含该笔交易相关信息)中,可以通过ctx.epoch()
读取到epoch
:表示当前系统的运行周期,大约每24小时更改一次;也可以通过ctx.epoch_timestamp_ms()
读取到当前周期开始时对应的时间戳。
通常,epoch
用于质押和系统操作,也可以用于模拟24小时周期等。
PTB¶
可编程交易块,允许用户在单笔交易中调用多个Move
函数,是生成交易的轻量而灵活的方式。在PTB
中,每个函数调用都按照顺序执行,甚至可以在后一个函数中使用前一个函数的返回值,一整笔交易是一个原子操作,即:其中任一调用失败,整笔交易失败,不会更改任何链上数据,唯一的损失可能就是需要支付一定量的gas
费。