1// Package englishauction is an idiomatic gno.land port of the classic Solidity2// English (ascending-bid) auction. Anyone can Start an auction for a named item3// with a duration in blocks; bidders call Bid with a strictly increasing amount.4// The previous highest bid becomes refundable (claim it with Withdraw). After5// the auction's end height anyone may call End, awarding the item to the highest6// bidder. Auctions and pending refunds live in ordered avl trees so Render can7// iterate deterministically.8package englishauction910import (11 "strconv"1213 "chain"14 "chain/runtime"15 "chain/runtime/unsafe"1617 "gno.land/p/nt/avl/v0"18)1920// Auction is a single ascending-bid auction.21type Auction struct {22 ID int6423 Seller address24 Item string25 EndHeight int6426 HighestBid uint6427 HighestBidder address28 Ended bool29}3031var (32 auctions avl.Tree // key(id) -> *Auction33 pending avl.Tree // "<id>:<addr>" -> uint64 refundable amount34 nextID int64 = 135)3637// key formats an id into a zero-padded, lexicographically-sortable string.38func key(id int64) string {39 s := strconv.FormatInt(id, 10)40 for len(s) < 12 {41 s = "0" + s42 }43 return s44}4546// pendKey formats the refund key for a given auction id and bidder.47func pendKey(id int64, bidder address) string {48 return key(id) + ":" + bidder.String()49}5051// Start opens a new auction for `item` running for `durationBlocks` blocks from52// the current height. The caller becomes the seller. Returns the auction id.53func Start(cur realm, item string, durationBlocks int64) int64 {54 if item == "" {55 panic("englishauction: item must not be empty")56 }57 if durationBlocks <= 0 {58 panic("englishauction: duration must be > 0 blocks")59 }60 seller := unsafe.PreviousRealm().Address()61 id := nextID62 nextID++6364 a := &Auction{65 ID: id,66 Seller: seller,67 Item: item,68 EndHeight: runtime.ChainHeight() + durationBlocks,69 }70 auctions.Set(key(id), a)7172 chain.Emit("Start",73 "id", strconv.FormatInt(id, 10),74 "seller", seller.String(),75 "item", item,76 "endHeight", strconv.FormatInt(a.EndHeight, 10),77 )78 return id79}8081// Bid places a bid of `amount` on auction `id`. It must strictly exceed the82// current highest bid. The displaced highest bid becomes refundable to its83// bidder via Withdraw.84func Bid(cur realm, id int64, amount uint64) {85 a := mustGet(id)86 if a.Ended || runtime.ChainHeight() >= a.EndHeight {87 panic("englishauction: auction has ended")88 }89 if amount <= a.HighestBid {90 panic("englishauction: bid must strictly exceed current highest")91 }92 bidder := unsafe.PreviousRealm().Address()93 if bidder == a.Seller {94 panic("englishauction: seller cannot bid")95 }9697 // The prior top bid becomes refundable (accumulate in case of repeats).98 if a.HighestBid > 0 {99 pk := pendKey(id, a.HighestBidder)100 pending.Set(pk, pendingOf(id, a.HighestBidder)+a.HighestBid)101 }102103 a.HighestBid = amount104 a.HighestBidder = bidder105106 chain.Emit("Bid",107 "id", strconv.FormatInt(id, 10),108 "bidder", bidder.String(),109 "amount", strconv.FormatUint(amount, 10),110 )111}112113// Withdraw refunds the caller's accumulated displaced bids for auction `id`.114// Returns the amount refunded (0 if nothing pending).115func Withdraw(cur realm, id int64) uint64 {116 mustGet(id) // ensure auction exists117 caller := unsafe.PreviousRealm().Address()118 amount := pendingOf(id, caller)119 if amount == 0 {120 return 0121 }122 pending.Remove(pendKey(id, caller))123 chain.Emit("Withdraw",124 "id", strconv.FormatInt(id, 10),125 "bidder", caller.String(),126 "amount", strconv.FormatUint(amount, 10),127 )128 return amount129}130131// End closes auction `id` once its end height is reached, awarding the item to132// the highest bidder (if any). Anyone may call it.133func End(cur realm, id int64) {134 a := mustGet(id)135 if a.Ended {136 panic("englishauction: auction already ended")137 }138 if runtime.ChainHeight() < a.EndHeight {139 panic("englishauction: auction not yet over")140 }141 a.Ended = true142143 chain.Emit("End",144 "id", strconv.FormatInt(id, 10),145 "winner", a.HighestBidder.String(),146 "amount", strconv.FormatUint(a.HighestBid, 10),147 )148}149150// --- read-only helpers (safe from tests and Render) ---151152// mustGet returns the auction for `id`, panicking if it does not exist.153func mustGet(id int64) *Auction {154 v := auctions.Get(key(id))155 if v == nil {156 panic("englishauction: no such auction")157 }158 return v.(*Auction)159}160161// pendingOf returns the refundable amount owed to `bidder` for auction `id`.162func pendingOf(id int64, bidder address) uint64 {163 v := pending.Get(pendKey(id, bidder))164 if v == nil {165 return 0166 }167 return v.(uint64)168}169170// blocksLeft returns how many blocks remain before `a` can be ended (0 if the171// end height has been reached).172func blocksLeft(a *Auction, height int64) int64 {173 if height >= a.EndHeight {174 return 0175 }176 return a.EndHeight - height177}178179// hasWinner reports whether the auction received at least one bid.180func hasWinner(a *Auction) bool {181 return a.HighestBid > 0182}183184// Render lists every auction with item, top bid + bidder, time left, and winner.185func Render(path string) string {186 out := "# English Auction\n\n"187 out += "Ascending-bid auctions. Start one, outbid others, and End it after "188 out += "its block deadline to award the item to the top bidder.\n\n"189190 if auctions.Size() == 0 {191 out += "_No auctions yet. Call `Start` to open one._\n"192 return out193 }194195 height := runtime.ChainHeight()196 out += "_Current block height: " + strconv.FormatInt(height, 10) + "_\n\n"197 out += "| ID | Item | Highest Bid | Bidder | Status | Winner |\n"198 out += "|---:|------|------------:|--------|--------|--------|\n"199200 auctions.Iterate("", "", func(_ string, v interface{}) bool {201 a := v.(*Auction)202203 bidStr := "—"204 bidderStr := "—"205 if hasWinner(a) {206 bidStr = strconv.FormatUint(a.HighestBid, 10)207 bidderStr = "`" + a.HighestBidder.String() + "`"208 }209210 status := ""211 winner := "—"212 switch {213 case a.Ended:214 status = "ended"215 if hasWinner(a) {216 winner = "`" + a.HighestBidder.String() + "`"217 } else {218 winner = "no bids"219 }220 case height >= a.EndHeight:221 status = "awaiting End"222 default:223 status = strconv.FormatInt(blocksLeft(a, height), 10) + " blocks left"224 }225226 out += "| " + strconv.FormatInt(a.ID, 10) + " | " + a.Item + " | " +227 bidStr + " | " + bidderStr + " | " + status + " | " + winner + " |\n"228 return false229 })230 return out231}232Bid(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 int64, amount uint64)
End(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 int64)
Render(path string) string
Start(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}, item string, durationBlocks int64) int64
Withdraw(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 int64) uint64
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.