Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Epoch 与时间

在区块链智能合约中,时间是一个重要但复杂的概念。Sui 提供了两种获取时间信息的方式:基于 Epoch 的粗粒度时间和基于 Clock 对象的毫秒级精确时间。理解两者的区别和适用场景,是实现时间相关业务逻辑(如锁仓、拍卖、限时活动)的关键。

Epoch 概述

什么是 Epoch

Epoch 是 Sui 网络的运行周期单位。每个 epoch 大约持续 24 小时(具体时长由网络治理决定)。在每个 epoch 结束时,网络会进行验证者集合更新、质押奖励分配等操作。

每个 epoch 有一个递增的编号(从 0 开始),以及一个起始时间戳。

从 TxContext 获取 Epoch 信息

TxContext 提供了两个与 epoch 相关的方法:

方法返回类型说明
ctx.epoch()u64当前 epoch 编号
ctx.epoch_timestamp_ms()u64当前 epoch 的开始时间戳(毫秒)
module examples::epoch_demo;

public struct EpochInfo has key {
    id: UID,
    epoch_number: u64,
    epoch_start_ms: u64,
    recorded_by: address,
}

public fun record_current_epoch(ctx: &mut TxContext) {
    let info = EpochInfo {
        id: object::new(ctx),
        epoch_number: ctx.epoch(),
        epoch_start_ms: ctx.epoch_timestamp_ms(),
        recorded_by: ctx.sender(),
    };
    transfer::transfer(info, ctx.sender());
}

Epoch 的特点

  • 粗粒度:一个 epoch 约 24 小时,无法精确到秒或毫秒
  • 稳定性:在同一个 epoch 内,ctx.epoch()ctx.epoch_timestamp_ms() 返回固定值
  • 低开销:从 TxContext 读取,不需要额外的共享对象输入
  • 适用场景:锁仓周期、质押奖励计算、功能开关等对时间精度要求不高的场景

基于 Epoch 的锁仓示例

module examples::epoch_lock;

public struct EpochVault has key {
    id: UID,
    owner: address,
    amount: u64,
    lock_until_epoch: u64,
}

/// 创建一个按 epoch 锁定的金库
public fun create_vault(
    amount: u64,
    lock_epochs: u64,
    ctx: &mut TxContext,
) {
    let vault = EpochVault {
        id: object::new(ctx),
        owner: ctx.sender(),
        amount,
        lock_until_epoch: ctx.epoch() + lock_epochs,
    };
    transfer::transfer(vault, ctx.sender());
}

/// 到达指定 epoch 后才能解锁
public fun unlock(vault: EpochVault, ctx: &TxContext): u64 {
    assert!(ctx.epoch() >= vault.lock_until_epoch, 0);
    assert!(ctx.sender() == vault.owner, 1);

    let EpochVault { id, owner: _, amount, lock_until_epoch: _ } = vault;
    id.delete();
    amount
}

/// 查询剩余锁定 epoch 数
public fun remaining_epochs(vault: &EpochVault, ctx: &TxContext): u64 {
    if (ctx.epoch() >= vault.lock_until_epoch) {
        0
    } else {
        vault.lock_until_epoch - ctx.epoch()
    }
}

Clock 对象

什么是 Clock

sui::clock::Clock 是一个系统级共享对象,位于固定地址 0x6。它由 Sui 系统在每个 checkpoint 时更新,提供毫秒级精度的时间戳,比 epoch 时间戳精确得多。

Clock 的特性

特性说明
地址固定为 0x6
类型共享不可变对象
引用方式只能以不可变引用 &Clock 传入
精度毫秒级
更新频率每个 checkpoint 更新一次

重要:Clock 对象只能以 &Clock(不可变引用)的形式在交易中使用。你不能获取 &mut Clock,因为它由系统独占更新。

使用 Clock

module examples::clock_demo;

use sui::clock::Clock;

public struct TimestampRecord has key {
    id: UID,
    recorded_at_ms: u64,
    recorder: address,
}

/// 记录当前精确时间戳
public fun record_time(clock: &Clock, ctx: &mut TxContext) {
    let record = TimestampRecord {
        id: object::new(ctx),
        recorded_at_ms: clock.timestamp_ms(),
        recorder: ctx.sender(),
    };
    transfer::transfer(record, ctx.sender());
}

/// 检查是否已过指定时间
public fun has_elapsed(clock: &Clock, since_ms: u64, duration_ms: u64): bool {
    clock.timestamp_ms() >= since_ms + duration_ms
}

时间锁定金库

module examples::time_lock;

use sui::clock::Clock;

public struct TimeLock has key {
    id: UID,
    unlock_time_ms: u64,
    content: vector<u8>,
    creator: address,
}

/// 创建一个时间锁定的金库
public fun create_lock(
    clock: &Clock,
    lock_duration_ms: u64,
    content: vector<u8>,
    ctx: &mut TxContext,
) {
    let lock = TimeLock {
        id: object::new(ctx),
        unlock_time_ms: clock.timestamp_ms() + lock_duration_ms,
        content,
        creator: ctx.sender(),
    };
    transfer::transfer(lock, ctx.sender());
}

/// 只有时间到达后才能解锁
public fun unlock(lock: TimeLock, clock: &Clock): vector<u8> {
    assert!(clock.timestamp_ms() >= lock.unlock_time_ms, 0);
    let TimeLock { id, unlock_time_ms: _, content, creator: _ } = lock;
    id.delete();
    content
}

/// 查询当前 epoch 信息
public fun current_epoch(ctx: &TxContext): u64 {
    ctx.epoch()
}

Epoch vs Clock 对比

维度EpochClock
精度~24 小时毫秒级
来源TxContext(自动提供)Clock 共享对象(需作为参数传入)
开销极低(无额外输入)需要输入共享对象(可能影响并行)
稳定性同一 epoch 内值不变每个 checkpoint 更新
适用场景锁仓周期、奖励计算拍卖、限时活动、精确计时

选择建议

  • 如果业务逻辑只需要“大约几天“的时间粒度,使用 epoch
  • 如果需要“几秒到几小时“的精确计时,使用 Clock
  • 如果同时需要两者,可以在同一个函数中同时使用 &Clock&TxContext

实际应用场景

限时拍卖

module examples::auction;

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

public struct AuctionCreated has copy, drop {
    auction_id: ID,
    end_time_ms: u64,
}

public struct BidPlaced has copy, drop {
    auction_id: ID,
    bidder: address,
    amount: u64,
}

public struct Auction has key {
    id: UID,
    seller: address,
    highest_bid: u64,
    highest_bidder: address,
    end_time_ms: u64,
    settled: bool,
}

public fun create_auction(
    clock: &Clock,
    duration_ms: u64,
    starting_bid: u64,
    ctx: &mut TxContext,
) {
    let end_time = clock.timestamp_ms() + duration_ms;
    let auction = Auction {
        id: object::new(ctx),
        seller: ctx.sender(),
        highest_bid: starting_bid,
        highest_bidder: @0x0,
        end_time_ms: end_time,
        settled: false,
    };

    event::emit(AuctionCreated {
        auction_id: object::id(&auction),
        end_time_ms: end_time,
    });

    transfer::share_object(auction);
}

public fun place_bid(
    auction: &mut Auction,
    bid_amount: u64,
    clock: &Clock,
    ctx: &TxContext,
) {
    assert!(!auction.settled, 0);
    assert!(clock.timestamp_ms() < auction.end_time_ms, 1);
    assert!(bid_amount > auction.highest_bid, 2);

    auction.highest_bid = bid_amount;
    auction.highest_bidder = ctx.sender();

    event::emit(BidPlaced {
        auction_id: object::id(auction),
        bidder: ctx.sender(),
        amount: bid_amount,
    });
}

public fun settle(auction: &mut Auction, clock: &Clock) {
    assert!(!auction.settled, 0);
    assert!(clock.timestamp_ms() >= auction.end_time_ms, 1);
    auction.settled = true;
}

冷却期机制

module examples::cooldown;

use sui::clock::Clock;

public struct Player has key {
    id: UID,
    last_action_ms: u64,
    cooldown_ms: u64,
    action_count: u64,
}

public fun create_player(cooldown_ms: u64, ctx: &mut TxContext) {
    let player = Player {
        id: object::new(ctx),
        last_action_ms: 0,
        cooldown_ms,
        action_count: 0,
    };
    transfer::transfer(player, ctx.sender());
}

public fun perform_action(player: &mut Player, clock: &Clock) {
    let now = clock.timestamp_ms();
    assert!(now >= player.last_action_ms + player.cooldown_ms, 0);

    player.last_action_ms = now;
    player.action_count = player.action_count + 1;
}

public fun time_until_ready(player: &Player, clock: &Clock): u64 {
    let ready_time = player.last_action_ms + player.cooldown_ms;
    let now = clock.timestamp_ms();
    if (now >= ready_time) {
        0
    } else {
        ready_time - now
    }
}

小结

Sui 提供了两种互补的时间机制。Epoch 来自 TxContext,表示网络运行周期(约 24 小时),适合粗粒度的时间逻辑,且无额外开销。Clock 是位于地址 0x6 的系统共享对象,提供毫秒级精度的时间戳,适合拍卖、冷却期、精确限时等场景,但需要作为 &Clock 引用传入函数。选择时间机制时,应根据业务需求的精度要求来决定:周期性逻辑优先使用 epoch,精确计时逻辑使用 Clock。两种机制可以在同一函数中组合使用。