Time & On-Chain Randomness

| | Sui | Aptos | |-|-----|-------| | API | sui::clock::Clock shared object at 0x6 | aptos_framework::timestamp | | Read | clock.timestamp_ms() | timestamp::now_seconds(), timestamp::now_microseconds() | | How to access | Pass clock: &Clock as a parameter (…

36.1 Time

| | Sui | Aptos | |-|-----|-------| | API | sui::clock::Clock shared object at 0x6 | aptos_framework::timestamp | | Read | clock.timestamp_ms() | timestamp::now_seconds(), timestamp::now_microseconds() | | How to access | Pass clock: &Clock as a parameter (tx.object.clock() in TS SDK) | Call directly | | Coarse alternative | ctx.epoch(), ctx.epoch_timestamp_ms() (no Clock needed, epoch ~24h) | n/a |

Block timestamps are set by validators and are approximately correct; don't rely on sub-second precision.

// Sui: a time-locked vault
use sui::clock::Clock;

public struct Locked<T: store> has key { id: UID, item: T, unlock_ms: u64, owner: address }

const ETooEarly: u64 = 0;
const ENotOwner: u64 = 1;

public fun unlock<T: store>(l: Locked<T>, clock: &Clock, ctx: &TxContext): T {
    assert!(clock.timestamp_ms() >= l.unlock_ms, ETooEarly);
    assert!(ctx.sender() == l.owner, ENotOwner);
    let Locked { id, item, unlock_ms: _, owner: _ } = l;
    id.delete();
    item
}
// Aptos
use aptos_framework::timestamp;
assert!(timestamp::now_seconds() >= unlock_time, E_TOO_EARLY);

36.2 Randomness

Both chains provide secure on-chain randomness generated by validators (threshold cryptography), unpredictable and unbiasable by any single party.

Make randomness consumers private entry (Sui: not public; Aptos: #[randomness] on a non-public entry function)

Otherwise another contract or PTB step could call it, inspect the result, and abort if unfavorable ("test-and-abort" attack) Keep gas cost of all outcomes similar

An attacker could set a gas budget that only covers the favorable path ("undergasing" attack)

Don't derive randomness from timestamps, tx digests, or object IDs They are predictable or manipulable

On Sui, put the Random usage in a separate command from anything that could depend on the result

The runtime restricts commands after a Random-using call to transfers/merges

// Sui: sui::random::Random shared object at 0x8
use sui::random::{Random, new_generator};

public struct DiceRolled has copy, drop { player: address, value: u8 }

entry fun roll_dice(r: &Random, ctx: &mut TxContext) {
    let mut gen = new_generator(r, ctx);
    let value = gen.generate_u8_in_range(1, 6);     // inclusive range
    sui::event::emit(DiceRolled { player: ctx.sender(), value });
}
// Aptos
#[randomness]
entry fun roll_dice(player: &signer) {
    let n = aptos_framework::randomness::u64_range(1, 7);   // [1, 7)
    // ...
}