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.

v0 source pure

Package varint implements LEB128 / protobuf-style variable-length integers over strings, with one deliberate restrict...

Readme View source

gno.land/p/moul/x/daily/varint/v0

LEB128 / protobuf-style variable-length integers over strings, canonical form only: PutUvarint, Uvarint, PutVarint, Varint, ZigZag, Len.

1import "gno.land/p/moul/x/daily/varint/v0"
2
3varint.PutUvarint(300)       // → "\xac\x02", two bytes instead of eight
4varint.PutVarint(-1)         // → "\x01", one byte instead of ten
5varint.Len(16384)            // → 3, without encoding anything
6varint.Uvarint("\xac\x02")   // → 300, 2, nil

A varint stores an integer in base 128, least significant group first, seven payload bits per byte and a continuation bit in the high position. Small numbers get small, which is the whole point: a counter under 128 costs one byte where a fixed uint64 costs eight, and on chain every byte of a persisted key or packed record is paid for once at write and again at every read.

Only the canonical encoding decodes, deliberately. Nothing in the base-128 scheme stops you padding a value with extra continuation groups: 80 00 and 00 both say zero. Protobuf accepts both. A realm that stores or hashes an encoded value cannot, because two spellings of one number are two different keys, two different hashes, and a cheap way to make the same state look like two states to anything comparing bytes. Uvarint returns ErrNotCanonical rather than quietly normalizing, so the caller finds out at the boundary instead of at the comparison.

Three behaviours worth knowing before you use it:

  • Signed values go through zigzag, not two's complement. Encoding -1 directly would sign-extend it to ten bytes, which is worse than not encoding it at all. Zigzag maps 0, -1, 1, -2, 2 onto 0, 1, 2, 3, 4, so magnitude decides the cost and the sign does not. ZigZag and UnZigZag are exported for callers packing their own frames.
  • Uvarint reads one value and reports how many bytes it took. It never looks past the value it decoded, so a stream is read by reslicing. Uvarints is the all-at-once form, and it refuses a trailing fragment with ErrTrailing rather than returning a silently short list: a partial value at the end is corruption, not data.
  • Encoding appends to a byte slice, decoding reads a string. AppendUvarint(dst []byte, x uint64) []byte is Go's encoding/binary shape, and it is a slice rather than a string because a string accumulator copies the whole prefix on every append, which makes packing a sequence quadratic in the encoded size. Decoding takes a string because that is what a realm has stored.
  • The errors are distinct for a reason. ErrTruncated means more input would have helped, ErrOverflow means the value cannot fit in 64 bits, ErrNotCanonical means the bytes decode fine but should never have been written. A caller validating untrusted input wants to tell the third from the first two.

Encoding is O(bytes out), at most ten iterations, and Len answers the size question without allocating. There is no streaming writer: the unit is a value, and a realm packing many of them calls AppendUvarint into one buffer in a loop, which is what PutUvarints does.

Live demo: r/moul/x/daily/varintdemo.


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

🧪 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 varint implements LEB128 / protobuf-style variable-length integers over strings, with one deliberate restriction: it only accepts the canonical encoding of a value.

A varint stores an integer in base 128, least significant group first. Each byte carries seven payload bits and a continuation bit in the high position, so values below 128 cost one byte, below 16384 two, and a full uint64 at most ten. Signed values go through zigzag first, which interleaves positives and negatives so that small magnitudes of either sign stay short.

Live demo: r/moul/x/daily/varintdemo(/r/moul/x/daily/varintdemo/v0).

Constants 1

const MaxLen

1const MaxLen = 10
source

MaxLen is the longest canonical encoding of a uint64: ten groups of seven bits covers 70 bits, which is the first multiple of seven past 64.

Variables 1

var ErrTruncated, ErrOverflow, ErrNotCanonical, ErrTrailing

 1var (
 2	// ErrTruncated is returned when the input ends inside a varint, meaning
 3	// the last byte read still had its continuation bit set.
 4	ErrTruncated = errors.New("varint: truncated")
 5	// ErrOverflow is returned when the encoded value does not fit in 64 bits.
 6	ErrOverflow = errors.New("varint: value overflows uint64")
 7	// ErrNotCanonical is returned for an encoding that is longer than it needs
 8	// to be, such as a value padded with trailing continuation groups.
 9	ErrNotCanonical = errors.New("varint: non-canonical encoding")
10	// ErrTrailing is returned by the all-at-once decoders when bytes remain
11	// after the last complete value.
12	ErrTrailing = errors.New("varint: trailing bytes")
13)
source

Functions 14

func AppendUvarint

1func AppendUvarint(dst []byte, x uint64) []byte
source

AppendUvarint appends the encoding of x to dst and returns the extended slice, the same shape as Go's encoding/binary.

It takes a byte slice and not a string on purpose: a string accumulator copies the whole prefix on every append, which makes packing a sequence quadratic in the encoded size. On chain that is gas paid to rebuild bytes the caller already had.

func AppendVarint

1func AppendVarint(dst []byte, x int64) []byte
source

AppendVarint appends the zigzagged encoding of x to dst.

func Len

1func Len(x uint64) int
source

Len reports how many bytes PutUvarint(x) would produce, without encoding it.

func LenVarint

1func LenVarint(x int64) int
source

LenVarint reports how many bytes PutVarint(x) would produce.

func PutUvarint

1func PutUvarint(x uint64) string
source

PutUvarint returns the canonical encoding of x.

func PutUvarints

1func PutUvarints(xs []uint64) string
source

PutUvarints encodes every value in xs back to back, into one buffer.

func PutVarint

1func PutVarint(x int64) string
source

PutVarint returns the canonical encoding of x, zigzagged first.

func PutVarints

1func PutVarints(xs []int64) string
source

PutVarints encodes every value in xs back to back, zigzagged, into one buffer.

func Uvarint

1func Uvarint(s string) (uint64, int, error)
source

Uvarint decodes the varint at the start of s and reports how many bytes it consumed. The rest of s is ignored, so a stream is read by reslicing.

func Uvarints

1func Uvarints(s string) ([]uint64, error)
source

Uvarints decodes a string that is nothing but varints. It fails rather than returning a partial result: a trailing fragment is corruption, not a value.

func Varint

1func Varint(s string) (int64, int, error)
source

Varint decodes the zigzagged varint at the start of s.

func Varints

1func Varints(s string) ([]int64, error)
source

Varints decodes a string that is nothing but zigzagged varints.

func ZigZag

1func ZigZag(x int64) uint64
source

ZigZag maps a signed value onto an unsigned one so that small magnitudes of either sign stay small: 0, -1, 1, -2, 2 become 0, 1, 2, 3, 4.

Dependencies

Imports 1

/p/g1magld5ptgzaktqcexxq0pgq5lmdf0kz6re8hlv/x/daily/varint/v0

Imported by indexer

Source Files 5