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/moul/kit/store/v0"18 "gno.land/p/nt/avl/v0"19)2021// Auction is a single ascending-bid auction. It carries no ID field: the id22// belongs to the store, which hands it back on lookup and iteration.23type Auction struct {24 Seller address25 Item string26 EndHeight int6427 HighestBid uint6428 HighestBidder address29 Ended bool30}3132var (33 // auctions assigns the auction ids. v0 kept its own nextID plus a key()34 // that zero-padded to width 12, which stopped ordering Render past 10^12.35 auctions = store.Named("englishauction: auction")3637 pending avl.Tree // "<id>:<addr>" -> uint64 refundable amount38)3940// pendKey formats the refund key for a given auction id and bidder. It uses41// the decimal id rather than the store key: pending is only ever point-read,42// never iterated, so it needs a readable key and not an ordered one.43func pendKey(id int64, bidder address) string {44 return strconv.FormatInt(id, 10) + ":" + bidder.String()45}4647// Start opens a new auction for `item` running for `durationBlocks` blocks from48// the current height. The caller becomes the seller. Returns the auction id.49func Start(cur realm, item string, durationBlocks int64) int64 {50 if item == "" {51 panic("englishauction: item must not be empty")52 }53 if durationBlocks <= 0 {54 panic("englishauction: duration must be > 0 blocks")55 }56 seller := unsafe.PreviousRealm().Address()57 a := &Auction{58 Seller: seller,59 Item: item,60 EndHeight: runtime.ChainHeight() + durationBlocks,61 }62 id := int64(auctions.Add(a))6364 chain.Emit("Start",65 "id", strconv.FormatInt(id, 10),66 "seller", seller.String(),67 "item", item,68 "endHeight", strconv.FormatInt(a.EndHeight, 10),69 )70 return id71}7273// Bid places a bid of `amount` on auction `id`. It must strictly exceed the74// current highest bid. The displaced highest bid becomes refundable to its75// bidder via Withdraw.76func Bid(cur realm, id int64, amount uint64) {77 a := mustGet(id)78 if a.Ended || runtime.ChainHeight() >= a.EndHeight {79 panic("englishauction: auction has ended")80 }81 if amount <= a.HighestBid {82 panic("englishauction: bid must strictly exceed current highest")83 }84 bidder := unsafe.PreviousRealm().Address()85 if bidder == a.Seller {86 panic("englishauction: seller cannot bid")87 }8889 // The prior top bid becomes refundable (accumulate in case of repeats).90 if a.HighestBid > 0 {91 pk := pendKey(id, a.HighestBidder)92 pending.Set(pk, pendingOf(id, a.HighestBidder)+a.HighestBid)93 }9495 a.HighestBid = amount96 a.HighestBidder = bidder9798 chain.Emit("Bid",99 "id", strconv.FormatInt(id, 10),100 "bidder", bidder.String(),101 "amount", strconv.FormatUint(amount, 10),102 )103}104105// Withdraw refunds the caller's accumulated displaced bids for auction `id`.106// Returns the amount refunded (0 if nothing pending).107func Withdraw(cur realm, id int64) uint64 {108 mustGet(id) // ensure auction exists109 caller := unsafe.PreviousRealm().Address()110 amount := pendingOf(id, caller)111 if amount == 0 {112 return 0113 }114 pending.Remove(pendKey(id, caller))115 chain.Emit("Withdraw",116 "id", strconv.FormatInt(id, 10),117 "bidder", caller.String(),118 "amount", strconv.FormatUint(amount, 10),119 )120 return amount121}122123// End closes auction `id` once its end height is reached, awarding the item to124// the highest bidder (if any). Anyone may call it.125func End(cur realm, id int64) {126 a := mustGet(id)127 if a.Ended {128 panic("englishauction: auction already ended")129 }130 if runtime.ChainHeight() < a.EndHeight {131 panic("englishauction: auction not yet over")132 }133 a.Ended = true134135 chain.Emit("End",136 "id", strconv.FormatInt(id, 10),137 "winner", a.HighestBidder.String(),138 "amount", strconv.FormatUint(a.HighestBid, 10),139 )140}141142// --- read-only helpers (safe from tests and Render) ---143144// mustGet returns the auction for `id`, panicking if it does not exist. The145// store's label puts the id in the message, which v0's fixed string146// ("englishauction: no such auction") left out.147func mustGet(id int64) *Auction {148 return auctions.MustGet(store.ID(id)).(*Auction)149}150151// pendingOf returns the refundable amount owed to `bidder` for auction `id`.152func pendingOf(id int64, bidder address) uint64 {153 v := pending.Get(pendKey(id, bidder))154 if v == nil {155 return 0156 }157 return v.(uint64)158}159160// blocksLeft returns how many blocks remain before `a` can be ended (0 if the161// end height has been reached).162func blocksLeft(a *Auction, height int64) int64 {163 if height >= a.EndHeight {164 return 0165 }166 return a.EndHeight - height167}168169// hasWinner reports whether the auction received at least one bid.170func hasWinner(a *Auction) bool {171 return a.HighestBid > 0172}173174// Render lists every auction with item, top bid + bidder, time left, and winner.175func Render(path string) string {176 out := "# English Auction\n\n"177 out += "Ascending-bid auctions. Start one, outbid others, and End it after "178 out += "its block deadline to award the item to the top bidder.\n\n"179180 if auctions.Len() == 0 {181 out += "_No auctions yet. Call `Start` to open one._\n"182 return out183 }184185 height := runtime.ChainHeight()186 out += "_Current block height: " + strconv.FormatInt(height, 10) + "_\n\n"187 out += "| ID | Item | Highest Bid | Bidder | Status | Winner |\n"188 out += "|---:|------|------------:|--------|--------|--------|\n"189190 auctions.Each(func(id store.ID, v any) {191 a := v.(*Auction)192193 bidStr := "—"194 bidderStr := "—"195 if hasWinner(a) {196 bidStr = strconv.FormatUint(a.HighestBid, 10)197 bidderStr = "`" + a.HighestBidder.String() + "`"198 }199200 status := ""201 winner := "—"202 switch {203 case a.Ended:204 status = "ended"205 if hasWinner(a) {206 winner = "`" + a.HighestBidder.String() + "`"207 } else {208 winner = "no bids"209 }210 case height >= a.EndHeight:211 status = "awaiting End"212 default:213 status = strconv.FormatInt(blocksLeft(a, height), 10) + " blocks left"214 }215216 out += "| " + id.String() + " | " + a.Item + " | " +217 bidStr + " | " + bidderStr + " | " + status + " | " + winner + " |\n"218 })219 return out220}221Bid(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.