1// Package timelock is a simplified port of OpenZeppelin's TimelockController.2//3// Operations are scheduled with a delay (measured in blocks). A scheduled4// operation becomes executable only once the chain height reaches the5// operation's ready height. Until then it can be cancelled by anyone (this6// simplified port omits role-based access control). Once executed or7// cancelled, an operation is terminal.8//9// Solidity mapping:10// - block.number -> runtime.ChainHeight()11// - msg.sender -> unsafe.PreviousRealm().Address()12// - require(...) -> panic(...)13// - events -> chain.Emit(...)14// - mapping(id => op) -> avl.Tree keyed by zero-padded id15package timelock1617import (18 "strconv"1920 "chain"21 "chain/runtime"22 "chain/runtime/unsafe"2324 "gno.land/p/nt/avl/v0"25 "gno.land/p/nt/ufmt/v0"26)2728// State labels for an operation.29const (30 StatePending = "Pending" // scheduled, not yet ready31 StateReady = "Ready" // ready height reached, awaiting execution32 StateExecuted = "Executed" // terminal33 StateCancelled = "Cancelled" // terminal34)3536// Operation is a single scheduled action.37type Operation struct {38 ID int39 Action string40 Proposer address41 Scheduled int64 // chain height at scheduling42 ReadyHeight int64 // executable once ChainHeight() >= ReadyHeight43 Done bool // executed44 Cancelled bool45}4647var (48 ops = avl.NewTree() // key: paddedKey(id) -> *Operation49 nextID = 150)5152// Schedule registers a new operation to run after delayBlocks blocks and53// returns its id. delayBlocks must be >= 0.54func Schedule(cur realm, action string, delayBlocks int) int {55 if action == "" {56 panic("timelock: empty action")57 }58 if delayBlocks < 0 {59 panic("timelock: negative delay")60 }6162 h := runtime.ChainHeight()63 id := nextID64 nextID++6566 op := &Operation{67 ID: id,68 Action: action,69 Proposer: unsafe.PreviousRealm().Address(),70 Scheduled: h,71 ReadyHeight: h + int64(delayBlocks),72 }73 ops.Set(paddedKey(id), op)7475 chain.Emit("Scheduled",76 "id", strconv.Itoa(id),77 "action", action,78 "readyHeight", strconv.FormatInt(op.ReadyHeight, 10),79 )80 return id81}8283// Execute runs a ready operation. It panics if the operation is unknown,84// already terminal, or not yet ready.85func Execute(cur realm, id int) {86 op := mustGet(id)87 if op.Done {88 panic("timelock: already executed")89 }90 if op.Cancelled {91 panic("timelock: operation cancelled")92 }93 if runtime.ChainHeight() < op.ReadyHeight {94 panic("timelock: operation not ready")95 }96 op.Done = true9798 chain.Emit("Executed", "id", strconv.Itoa(id), "action", op.Action)99}100101// Cancel drops a pending or ready operation before it executes. It panics if102// the operation is unknown or already terminal.103func Cancel(cur realm, id int) {104 op := mustGet(id)105 if op.Done {106 panic("timelock: already executed")107 }108 if op.Cancelled {109 panic("timelock: already cancelled")110 }111 op.Cancelled = true112113 chain.Emit("Cancelled", "id", strconv.Itoa(id), "action", op.Action)114}115116// mustGet returns the operation or panics if absent.117func mustGet(id int) *Operation {118 v := ops.Get(paddedKey(id))119 if v == nil {120 panic("timelock: unknown operation")121 }122 return v.(*Operation)123}124125// --- pure/read-only helpers (safe to unit-test) ---126127// paddedKey returns a fixed-width, lexicographically sortable key for an id so128// avl iteration yields operations in id order.129func paddedKey(id int) string {130 s := strconv.Itoa(id)131 for len(s) < 12 {132 s = "0" + s133 }134 return s135}136137// StateOf returns the display state of op at the given chain height.138func StateOf(op *Operation, height int64) string {139 switch {140 case op.Cancelled:141 return StateCancelled142 case op.Done:143 return StateExecuted144 case height >= op.ReadyHeight:145 return StateReady146 default:147 return StatePending148 }149}150151// BlocksRemaining returns how many blocks remain before op is ready (0 if152// already ready or past).153func BlocksRemaining(op *Operation, height int64) int64 {154 if height >= op.ReadyHeight {155 return 0156 }157 return op.ReadyHeight - height158}159160// Render implements the standard realm markdown view.161func Render(path string) string {162 h := runtime.ChainHeight()163 out := "# Timelock Controller\n\n"164 out += ufmt.Sprintf("Current chain height: **%d**\n\n", h)165166 if ops.Size() == 0 {167 out += "_No operations scheduled yet._\n\n"168 out += "Call `Schedule(action, delayBlocks)` to queue one.\n"169 return out170 }171172 out += ufmt.Sprintf("Total operations: **%d**\n\n", ops.Size())173 out += "| ID | Action | State | Ready @ | Blocks left | Proposer |\n"174 out += "|---:|--------|-------|--------:|------------:|----------|\n"175176 ops.Iterate("", "", func(_ string, v any) bool {177 op := v.(*Operation)178 state := StateOf(op, h)179 left := BlocksRemaining(op, h)180 out += ufmt.Sprintf("| %d | %s | %s | %d | %d | %s |\n",181 op.ID, op.Action, state, op.ReadyHeight, left, op.Proposer.String())182 return false183 })184 return out185}186BlocksRemaining(op *gno.land/r/moul/x/daily/timelock/v0.Operation, height int64) int64
Cancel(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, id int)
Execute(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, id int)
Render(path string) string
Schedule(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, action string, delayBlocks int) int
StateOf(op *gno.land/r/moul/x/daily/timelock/v0.Operation, height int64) string
Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.
vm/qrender output, sanitized (docs/render-security.md) and displayed in an empty-sandbox iframe — scripts, forms and popups cannot run. Links stay inert in-preview; right-click to open.