const MaxLen
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.
Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.
Package varint implements LEB128 / protobuf-style variable-length integers over strings, with one deliberate restrict...
gno.land/p/moul/x/daily/varint/v0LEB128 / 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:
-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.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.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.
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).
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)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.
AppendVarint appends the zigzagged encoding of x to dst.
Len reports how many bytes PutUvarint(x) would produce, without encoding it.
LenVarint reports how many bytes PutVarint(x) would produce.
PutUvarint returns the canonical encoding of x.
PutUvarints encodes every value in xs back to back, into one buffer.
PutVarint returns the canonical encoding of x, zigzagged first.
PutVarints encodes every value in xs back to back, zigzagged, into one buffer.
UnZigZag is the inverse of ZigZag.
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.
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.
Varint decodes the zigzagged varint at the start of s.
Varints decodes a string that is nothing but zigzagged varints.
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.