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

daoauth.gno

6.27 Kb · 224 lines
  1// Package daoauth writes the arguments of a governed action as one typed,
  2// canonical string. memba_gov stores that string with the proposal and the
  3// target realm rebuilds it from its real arguments when it consumes the
  4// approval: the two must be equal byte for byte. Each value has exactly one
  5// spelling, so equal actions always encode to equal strings.
  6//
  7// Fields are joined by "|":
  8//
  9//	u:<uint64>        decimal, no sign, no leading zero
 10//	i:<int64>         decimal, "-" only when negative, no leading zero
 11//	b:0 or b:1        bool
 12//	a:<address>       the lowercase bech32 text the chain gives a caller
 13//	s:<len>:<text>    length-prefixed printable ASCII (0x20-0x7E)
 14//
 15// Strings are compared as raw bytes, with no Unicode normalisation; text is
 16// printable ASCII so that what a voter reads is exactly what executes.
 17package daoauth
 18
 19import (
 20	"errors"
 21	"strconv"
 22	"strings"
 23)
 24
 25const (
 26	MaxArgs = 1024 // bytes of one encoded argument string
 27	MaxText = 512  // bytes of one string field
 28)
 29
 30var (
 31	ErrSyntax  = errors.New("daoauth: malformed argument string")
 32	ErrAddress = errors.New("daoauth: address is not lowercase bech32")
 33	ErrText    = errors.New("daoauth: text must be printable ASCII of at most 512 bytes")
 34	ErrLength  = errors.New("daoauth: arguments exceed 1024 bytes")
 35)
 36
 37// Args accumulates fields in order. Methods panic on a value that has no
 38// canonical form, so a realm can never build an approval nobody can match.
 39type Args struct{ fields []string }
 40
 41func New() *Args { return &Args{} }
 42
 43func (a *Args) Uint(v uint64) *Args { return a.add("u:" + strconv.FormatUint(v, 10)) }
 44func (a *Args) Int(v int64) *Args   { return a.add("i:" + strconv.FormatInt(v, 10)) }
 45
 46func (a *Args) Bool(v bool) *Args {
 47	if v {
 48		return a.add("b:1")
 49	}
 50	return a.add("b:0")
 51}
 52
 53func (a *Args) Addr(v address) *Args {
 54	if !ValidAddress(v) {
 55		panic(ErrAddress.Error())
 56	}
 57	return a.add("a:" + string(v))
 58}
 59
 60func (a *Args) Str(v string) *Args {
 61	if !ValidText(v) {
 62		panic(ErrText.Error())
 63	}
 64	return a.add("s:" + strconv.Itoa(len(v)) + ":" + v)
 65}
 66
 67func (a *Args) add(f string) *Args {
 68	a.fields = append(a.fields, f)
 69	return a
 70}
 71
 72// String returns the encoding. It panics past MaxArgs.
 73func (a *Args) String() string {
 74	s := strings.Join(a.fields, "|")
 75	if len(s) > MaxArgs {
 76		panic(ErrLength.Error())
 77	}
 78	return s
 79}
 80
 81// Field is one decoded value: Tag is 'u', 'i', 'b', 'a' or 's', and Value
 82// is its text, without the length prefix for strings.
 83type Field struct {
 84	Tag   byte
 85	Value string
 86}
 87
 88// Parse decodes s and refuses any text that is not the canonical encoding of
 89// its own fields. The empty string is the encoding of no fields.
 90func Parse(s string) ([]Field, error) {
 91	if len(s) > MaxArgs {
 92		return nil, ErrLength
 93	}
 94	var out []Field
 95	for len(s) > 0 {
 96		if len(s) < 2 || s[1] != ':' {
 97			return nil, ErrSyntax
 98		}
 99		tag, rest := s[0], s[2:]
100		var v string
101		if tag == 's' {
102			i := strings.IndexByte(rest, ':')
103			if i < 1 {
104				return nil, ErrSyntax
105			}
106			n, err := strconv.Atoi(rest[:i])
107			if err != nil || n < 0 || strconv.Itoa(n) != rest[:i] || n > len(rest)-i-1 {
108				return nil, ErrSyntax
109			}
110			v, rest = rest[i+1:i+1+n], rest[i+1+n:]
111			if !ValidText(v) {
112				return nil, ErrText
113			}
114		} else {
115			i := strings.IndexByte(rest, '|')
116			if i < 0 {
117				i = len(rest)
118			}
119			v, rest = rest[:i], rest[i:]
120			if err := checkScalar(tag, v); err != nil {
121				return nil, err
122			}
123		}
124		out = append(out, Field{Tag: tag, Value: v})
125		if len(rest) > 0 {
126			if rest[0] != '|' || len(rest) == 1 {
127				return nil, ErrSyntax
128			}
129			rest = rest[1:]
130		}
131		s = rest
132	}
133	return out, nil
134}
135
136func checkScalar(tag byte, v string) error {
137	switch tag {
138	case 'u':
139		n, err := strconv.ParseUint(v, 10, 64)
140		if err != nil || strconv.FormatUint(n, 10) != v {
141			return ErrSyntax
142		}
143	case 'i':
144		n, err := strconv.ParseInt(v, 10, 64)
145		if err != nil || strconv.FormatInt(n, 10) != v {
146			return ErrSyntax
147		}
148	case 'b':
149		if v != "0" && v != "1" {
150			return ErrSyntax
151		}
152	case 'a':
153		if !ValidAddress(address(v)) {
154			return ErrAddress
155		}
156	default:
157		return ErrSyntax
158	}
159	return nil
160}
161
162// ValidText reports whether s is at most MaxText bytes of printable ASCII
163// (0x20 to 0x7E): no control, invisible, combining or direction-changing
164// character can make a value read differently from what executes.
165func ValidText(s string) bool {
166	if len(s) > MaxText {
167		return false
168	}
169	for i := 0; i < len(s); i++ {
170		if s[i] < 0x20 || s[i] > 0x7e {
171			return false
172		}
173	}
174	return true
175}
176
177// The chain decodes four texts to one address: the lowercase bech32 text it
178// gives a caller, that text in capitals, and the bech32m checksum in either
179// case. Only the first ever equals cur.Previous().Address(). The check below
180// is the one of launchpad meta/v1, copied rather than imported so governance
181// does not depend on the Launchpad release. It reads the text instead of
182// decoding it.
183
184// bech32Value holds, for each character of the bech32 alphabet, its 5-bit
185// value plus one. Zero marks every other byte, capitals included.
186var bech32Value = [256]byte{
187	'q': 1, 'p': 2, 'z': 3, 'r': 4, 'y': 5, '9': 6, 'x': 7, '8': 8,
188	'g': 9, 'f': 10, '2': 11, 't': 12, 'v': 13, 'd': 14, 'w': 15, '0': 16,
189	's': 17, '3': 18, 'j': 19, 'n': 20, '5': 21, '4': 22, 'k': 23, 'h': 24,
190	'c': 25, 'e': 26, '6': 27, 'm': 28, 'u': 29, 'a': 30, '7': 31, 'l': 32,
191}
192
193// bech32Gen[t] is the XOR of the BIP-173 generators selected by the bits of t.
194var bech32Gen = [32]uint32{
195	0x00000000, 0x3b6a57b2, 0x26508e6d, 0x1d3ad9df,
196	0x1ea119fa, 0x25cb4e48, 0x38f19797, 0x039bc025,
197	0x3d4233dd, 0x0628646f, 0x1b12bdb0, 0x2078ea02,
198	0x23e32a27, 0x18897d95, 0x05b3a44a, 0x3ed9f3f8,
199	0x2a1462b3, 0x117e3501, 0x0c44ecde, 0x372ebb6c,
200	0x34b57b49, 0x0fdf2cfb, 0x12e5f524, 0x298fa296,
201	0x1756516e, 0x2c3c06dc, 0x3106df03, 0x0a6c88b1,
202	0x09f74894, 0x329d1f26, 0x2fa7c6f9, 0x14cd914b,
203}
204
205// bech32PrefixState is the checksum state after the prefix "g" (3, 0, 7).
206const bech32PrefixState uint32 = 0x8c07
207
208// ValidAddress reports whether a is "g1" and 38 lowercase bech32 characters
209// whose checksum is the original bech32 one.
210func ValidAddress(a address) bool {
211	s := string(a)
212	if len(s) != 40 || s[0] != 'g' || s[1] != '1' {
213		return false
214	}
215	chk := bech32PrefixState
216	for i := 2; i < 40; i++ {
217		v := bech32Value[s[i]]
218		if v == 0 {
219			return false
220		}
221		chk = (chk&0x1ffffff)<<5 ^ uint32(v-1) ^ bech32Gen[chk>>25]
222	}
223	return chk == 1
224}