Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

term.gno

4.65 Kb · 135 lines
  1package nslogic
  2
  3import (
  4	"strconv"
  5	"time"
  6
  7	"gno.land/p/nt/ufmt/v0"
  8	"gno.land/r/nym-mnsrehearsal001/nsdata/v2"
  9)
 10
 11// How far ahead a name may be paid.
 12//
 13// Renewal was unbounded. RenewNameRecord adds one term to the previous
 14// due date and asks no further questions, so a holder pressing renew
 15// seven times bought seven years of runway, and nothing anywhere said
 16// that was not intended. It is a bug in two directions at once:
 17//
 18//   - Free domains have a base price of zero. Renewal is free too, so a
 19//     script could take every good name under *gnoland and hold it for a
 20//     century for the cost of gas. There is no economic brake on a free
 21//     name at all; the term limit IS the brake.
 22//   - A paid name held fifty years ahead is fifty years of a promise we
 23//     have made against a rate written by hand and a testnet that will
 24//     be replaced. Selling that is easy and honouring it is not.
 25//
 26// The limit is RUNWAY, not total lifetime: at most N years may be
 27// outstanding at any moment. Nobody is stopped from holding a name
 28// forever — they renew as the runway burns down, which is the same
 29// arrangement every domain registrar has always used and the one people
 30// already understand.
 31//
 32// Per domain first, then a project-wide default, so a free domain can be
 33// capped at one year without capping anything else.
 34const (
 35	keyMaxTerm      = "maxterm" // domain extra AND global: years of runway
 36	defaultMaxTerm  = int64(5)
 37	maxMaxTermYears = int64(100)
 38)
 39
 40// MaxTermYears is the cap that applies to names under this domain.
 41func MaxTermYears(domainLabel string) int64 {
 42	if v := nsdata.GetDomainExtra(domainLabel, keyMaxTerm); v != "" {
 43		if y, err := strconv.ParseInt(v, 10, 64); err == nil && y > 0 {
 44			return y
 45		}
 46	}
 47	if v := nsdata.GetGlobal(keyMaxTerm); v != "" {
 48		if y, err := strconv.ParseInt(v, 10, 64); err == nil && y > 0 {
 49			return y
 50		}
 51	}
 52	return defaultMaxTerm
 53}
 54
 55// SetMaxTerm caps a single domain, or the whole project when the domain
 56// label is empty. A domain's own cap always wins, so tightening one free
 57// domain does not disturb the rest.
 58func SetMaxTerm(cur realm, domainLabel string, years int64) {
 59	assertNoSend(cur)
 60	assertIsAdmin(cur)
 61	emitAdmin(cur, "SetMaxTerm", "domain", domainLabel, "years", fmtInt(years))
 62	if years < 1 || years > maxMaxTermYears {
 63		panic("nslogic: term cap must be 1-100 years")
 64	}
 65	v := strconv.FormatInt(years, 10)
 66	if domainLabel == "" {
 67		setGlobal(cur, keyMaxTerm, v)
 68		return
 69	}
 70	if !nsdata.DomainExists(domainLabel) {
 71		panic("nslogic: unknown domain")
 72	}
 73	if err := nsdata.SetDomainExtraRecord(cross(cur), domainLabel, keyMaxTerm, v); err != nil {
 74		panic(err)
 75	}
 76}
 77
 78// RenewalsLeft is how many more years may be added to a name right now.
 79// Zero means the renew button should be off, and the site reads this
 80// rather than doing the arithmetic itself — the cap can be per domain,
 81// and a page that guesses it will guess wrong on exactly the domains
 82// where it matters.
 83//
 84// An unregistered name reports the full allowance: nothing is holding
 85// runway yet.
 86func RenewalsLeft(label, domainLabel string) int64 {
 87	maxYears := MaxTermYears(domainLabel)
 88	if !nsdata.NameExists(label, domainLabel) {
 89		return maxYears
 90	}
 91	term := nsdata.GetTermLength()
 92	if term <= 0 {
 93		return 0
 94	}
 95	_, _, expires, _, _, _ := nsdata.GetNameInfo(label, domainLabel)
 96	return renewalsLeft(expires, time.Now().Unix(), term, maxYears)
 97}
 98
 99// renewalsLeft is the arithmetic on its own, so it can be tested without
100// a chain under it.
101//
102// Runway is measured from NOW, not from the registration date, so a name
103// deep in its grace period counts as zero rather than as a negative that
104// would then read as extra room. Years already bought round UP: a name
105// with thirteen months left is holding two of its five, because the
106// alternative lets somebody sit permanently at four-years-and-eleven-
107// months and renew every month forever.
108func renewalsLeft(expires, now, term, maxYears int64) int64 {
109	if term <= 0 {
110		return 0
111	}
112	runway := expires - now
113	if runway < 0 {
114		runway = 0
115	}
116	left := maxYears - (runway+term-1)/term
117	if left < 0 {
118		return 0
119	}
120	return left
121}
122
123// assertRoomToRenew is the enforcement. It runs before payment is taken,
124// so a rejected renewal costs gas and nothing else — taking the money
125// first and reverting would work too, but only because a panic reverts,
126// and depending on that for a refund is a habit that eventually meets a
127// path where it is not true.
128func assertRoomToRenew(label, domainLabel string) {
129	if RenewalsLeft(label, domainLabel) > 0 {
130		return
131	}
132	panic(ufmt.Sprintf(
133		"nslogic: %s*%s is already paid %d years ahead, which is the limit. Renew again when it has run down.",
134		label, domainLabel, MaxTermYears(domainLabel)))
135}