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

v0 source pure

Package storagecost answers one question: is it worth paying gas to delete on-chain state?

Readme View source

gno.land/p/moul/x/storagecost/v0

Is it worth paying gas to delete on-chain state?

On gno.land every byte of realm state locks GNOT, and the lock is refunded to whoever signs the transaction that frees the byte. Deleting state is therefore paid work. Whether a particular deletion pays depends on two prices that move independently: the storage price, a chain parameter, and the gas price of the day. A contract cannot know the second one, so it cannot decide on its own behalf whether to compact, reindex or reap. It can only publish the size of the prize and let a caller do the arithmetic.

This package is that arithmetic: pure integer maths, no chain imports, so a realm can call it inside a Render and an off-chain bot can reuse the identical formula.

The one number to remember

One byte freed refunds 100 ugnot. At the lowest gas price mainnet has actually accepted, one ugnot buys 1000 gas. So a byte is worth 100,000 gas, and any deletion costing less than that per byte pays for itself.

1q := storagecost.EvaluateAtFloor(17835, 5_000_000)
2q.Refund // 1783500 ugnot
3q.Fee    //    5000 ugnot
4q.Net    // 1778500 ugnot
5q.Worth() // true
6q.String() // "17835 bytes, refunds 1.7835 GNOT against 0.005 GNOT of gas, break-even 50 bytes: worth 1.7785 GNOT"

What is in here

Refund · BreakEvenBytes · Net the core arithmetic. BreakEvenBytes rounds up, so a quoted threshold always covers the fee
GasFee · FloorGasFee a fee from a gas ceiling and a rational gas price. The fee tracks gas_wanted, not gas_used, so unused headroom is paid for
Evaluate · EvaluateAtFloor · Quote a whole verdict for one candidate cleanup, with a String() fit for a Render
EstimateBytes what a payload really costs once a realm has wrapped it in an object, at the measured 1.85x
FormatGNOT ugnot as readable GNOT, because gno has no floats and a bounty quoted in ugnot is unreadable

Two honesty notes

DefaultStoragePrice is a default, not a fact. vm:p:storage_price is governance settable. Read it from the chain when real money depends on the answer; the constant is for sizing and display.

EstimateBytes is an estimate. It exists so a bounty shown on a page is within a factor of two instead of reporting raw payload length. No stdlib call exposes a realm's real locked storage, so the authoritative numbers are the chain's: the vm/qstorage query, or the StorageDepositEvent and StorageUnlockEvent every transaction emits. Never settle an accounting question with a guess.

Demo realm: r/moul/x/reaper.


Part of moul/gno-contracts — moul's versioned gno.land contracts. See the repository for the full catalog, build/test tooling, and usage.

Dependency graph:

gno.land/p/moul/x/storagecost/v0 dependency graph

🧪 Highly experimental — potentially vibe-coded. Not audited; may break, change, or be removed at any time. Do not use with anything of value. Full disclaimer: DISCLAIMER.

Overview

Package storagecost answers one question: is it worth paying gas to delete on-chain state?

On gno.land every byte of realm state locks GNOT, and the lock is refunded to whoever signs the transaction that frees the byte. Deleting state is therefore paid work, and whether a given deletion pays depends on two prices that move independently: the storage price, a chain parameter, and the gas price of the day. A contract cannot know the second one, so it cannot decide on its own behalf whether to compact, reindex or reap. It can only publish the size of the prize and let a caller do the arithmetic.

This package is that arithmetic. It is pure integer maths with no chain imports, so a realm can call it inside a Render and an off-chain bot can reuse the identical formula.

The single fact worth remembering is the ratio. One byte freed refunds 100 ugnot, and at the lowest gas price mainnet has actually accepted one ugnot buys 1000 gas. So a byte is worth 100,000 gas, and any deletion costing less than that per byte pays for itself.

Demo realm: gno.land/r/moul/x/reaper/v0.

Constants 1

const DefaultStoragePrice, GasPerUgnotFloor, payloadOverheadNum, payloadOverheadDen, ugnotPerGNOT

 1const (
 2	// DefaultStoragePrice is the ugnot locked per byte of realm state, the
 3	// default of the chain's vm:p:storage_price parameter. It is governance
 4	// settable, so read it from the chain rather than trusting this constant
 5	// when real money depends on the answer.
 6	DefaultStoragePrice int64 = 100
 7
 8	// GasPerUgnotFloor is how much gas one ugnot buys at the lowest gas price
 9	// mainnet has been observed to accept, 0.001 ugnot per gas. It is a floor,
10	// not a promise: the fee a node requires tracks gas_wanted, so asking for
11	// more headroom raises the fee proportionally.
12	GasPerUgnotFloor int64 = 1000
13
14	// payloadOverheadNum/payloadOverheadDen approximate what a payload really
15	// costs once the realm has wrapped it in an object. Measured at 1.85x: ten
16	// 1,024-byte strings in a realm slice cost 18,984 bytes of state, 1,898
17	// each. It is an estimate and nothing more. The authoritative number is
18	// the chain's own, from the vm/qstorage query or a StorageDepositEvent.
19	payloadOverheadNum int64 = 185
20	payloadOverheadDen int64 = 100
21
22	ugnotPerGNOT int64 = 1_000_000
23)
source

Functions 8

func BreakEvenBytes

1func BreakEvenBytes(feeUgnot, pricePerByte int64) int64
source

BreakEvenBytes is the fewest bytes whose refund covers a fee: the point where a cleanup stops costing money and starts making it. Below this many bytes the transaction is charity.

func EstimateBytes

1func EstimateBytes(payloadBytes int64) int64
source

EstimateBytes guesses the realm state a payload of this many bytes will occupy, applying the measured object overhead.

It is for sizing a bounty in a Render, where being within a factor of two beats reporting the raw payload length. Never settle an accounting question with it.

func FloorGasFee

1func FloorGasFee(gasWanted int64) int64
source

FloorGasFee is GasFee at the lowest gas price mainnet has accepted.

func FormatGNOT

1func FormatGNOT(amount int64) string
source

FormatGNOT renders ugnot as GNOT with six decimal places and trailing zeros trimmed, because a bounty shown in ugnot is unreadable and gno has no floats.

func GasFee

1func GasFee(gasWanted, num, den int64) int64
source

GasFee is the fee a transaction asking for gasWanted must pay at a gas price of num/den ugnot per gas, rounded up.

The fee tracks gas_wanted rather than gas_used, so unused headroom is paid for. Pass the ceiling you will actually put in the transaction, not what you expect to burn.

func Refund

1func Refund(bytes, pricePerByte int64) int64
source

Refund is the deposit returned for freeing bytes at the given price per byte. Returns 0 for non-positive inputs rather than panicking, so a Render on a realm with no state still works.

func Evaluate

1func Evaluate(bytes, pricePerByte, gasWanted, num, den int64) Quote
source

Evaluate prices one cleanup: freeing bytes in a transaction asking for gasWanted, at a storage price of pricePerByte and a gas price of num/den ugnot per gas.

func EvaluateAtFloor

1func EvaluateAtFloor(bytes, gasWanted int64) Quote
source

EvaluateAtFloor is Evaluate at the default storage price and the floor gas price: the best case, and the one to quote when advertising a bounty.

Types 1

type Quote

struct
1type Quote struct {
2	Bytes     int64 // bytes the cleanup would free
3	Refund    int64 // ugnot returned for them
4	Fee       int64 // ugnot the transaction will cost
5	Net       int64 // Refund - Fee; negative means it costs more than it pays
6	BreakEven int64 // bytes needed to cover Fee
7}
source

Quote is a complete answer for one candidate cleanup.

Methods on Quote

func String

method on Quote
1func (q Quote) String() string
source

String renders the quote as one line of markdown-safe text, for a Render.

func Worth

method on Quote
1func (q Quote) Worth() bool
source

Worth reports whether the cleanup pays for itself.

Imports 1

Source Files 4