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.

fee.gno

4.25 Kb · 140 lines
  1package nsmarket
  2
  3import (
  4	"strconv"
  5	"strings"
  6
  7	"gno.land/r/nym-mnsrehearsal001/nsdata/v2"
  8)
  9
 10// The house fee, expressed the same way the pricing rules are so there
 11// is one convention on this project and not two:
 12//
 13//	"1000000"  a flat amount in ugnot — 1 GNOT
 14//	"%3"       three percent of the sale
 15//	"%2.5"     two and a half percent; one decimal place is honoured
 16//
 17// Stored in the vault as a global so it can be changed without
 18// redeploying anything, read fresh on every sale so a change takes
 19// effect immediately rather than at the next deploy.
 20const (
 21	keyFee     = "marketfee"
 22	defaultFee = "1000000" // 1 GNOT
 23
 24	// A flat fee is a percentage in disguise on a cheap name: 1 GNOT on
 25	// a 0.5 GNOT sale is 200%. Rather than forbid cheap listings or let
 26	// a seller be charged more than they earn, the fee is capped at half
 27	// the sale. The listing screen shows the seller the actual number
 28	// before they sign, which is the part that matters.
 29	maxFeeBps = int64(5000) // 50%
 30	bpsDenom  = int64(10000)
 31)
 32
 33func FeeSpec() string {
 34	if v := nsdata.GetGlobal(keyFee); v != "" {
 35		return v
 36	}
 37	return defaultFee
 38}
 39
 40// SetFee changes it. Admin only, and validated here rather than trusted,
 41// because a malformed spec would otherwise be read on every sale and
 42// silently resolve to zero.
 43/*
 44SetFee CANNOT WORK FROM HERE, and saying so is the fix.
 45
 46nsdata.SetGlobalRecord opens with assertIsTrustedLogic, and this realm is
 47deliberately outside the trusted set — types.gno says so in capitals, and
 48the deploy panel actively revokes anything but nslogic. So every call
 49here passed its own admin check and its own validation and then panicked
 50inside the vault, for everybody, including the correct admin. A dead
 51control that reads as live is worse than no control: it fails at signing
 52time, during the incident that prompted reaching for it.
 53
 54Granting nsmarket vault trust to fix this would surrender the property
 55the whole realm is built around. So the setter moved to nslogic, which
 56already has the grant, and runs the same parseFee validation there.
 57*/
 58func SetFee(cur realm, spec string) {
 59	panic("nsmarket: fees are set with nslogic.SetMarketFee — this realm has no vault privileges and never did")
 60}
 61
 62// parseFee returns (flat ugnot, basis points, error). Exactly one of the
 63// first two is non-zero.
 64func parseFee(spec string) (int64, int64, string) {
 65	spec = strings.TrimSpace(spec)
 66	if spec == "" {
 67		return 0, 0, ""
 68	}
 69	if !strings.HasPrefix(spec, "%") {
 70		n, err := strconv.ParseInt(spec, 10, 64)
 71		if err != nil || n < 0 {
 72			return 0, 0, "fee must be an amount in ugnot or a percentage like %2.5 — got " + spec
 73		}
 74		return n, 0, ""
 75	}
 76
 77	// A percentage, carried as basis points so "%2.5" is expressible
 78	// without floating point anywhere near money.
 79	body := spec[1:]
 80	whole, frac := body, ""
 81	if dot := strings.Index(body, "."); dot >= 0 {
 82		whole, frac = body[:dot], body[dot+1:]
 83	}
 84	if whole == "" {
 85		whole = "0"
 86	}
 87	w, err := strconv.ParseInt(whole, 10, 64)
 88	if err != nil || w < 0 {
 89		return 0, 0, "bad percentage — " + spec
 90	}
 91	bps := w * 100
 92	if frac != "" {
 93		if len(frac) > 1 {
 94			return 0, 0, "one decimal place at most — " + spec
 95		}
 96		f, err2 := strconv.ParseInt(frac, 10, 64)
 97		if err2 != nil || f < 0 {
 98			return 0, 0, "bad percentage — " + spec
 99		}
100		bps += f * 10
101	}
102	if bps > maxFeeBps {
103		return 0, 0, "a fee above 50% is not a marketplace — " + spec
104	}
105	return 0, bps, ""
106}
107
108// FeeFor is what the house takes on a sale at this price, and what the
109// seller is left with. Exported so the listing screen can show both
110// before anybody signs — a fee somebody discovers afterwards is a fee
111// they were not told about.
112func FeeFor(price int64) (fee, toSeller int64) {
113	if price <= 0 {
114		return 0, 0
115	}
116	flat, bps, err := parseFee(FeeSpec())
117	if err != "" {
118		return 0, price // an unreadable spec charges nothing; it never overcharges
119	}
120	if bps > 0 {
121		fee = price * bps / bpsDenom
122	} else {
123		fee = flat
124	}
125	// The cap, applied last, so it bounds a flat fee and a percentage
126	// alike however the spec was written.
127	if ceiling := price * maxFeeBps / bpsDenom; fee > ceiling {
128		fee = ceiling
129	}
130	if fee < 0 {
131		fee = 0
132	}
133	return fee, price - fee
134}
135
136func assertIsAdmin(cur realm) {
137	if cur.Previous().Address() != nsdata.GetAdmin() {
138		panic("nsmarket: restricted to project admin")
139	}
140}