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 the3// target realm rebuilds it from its real arguments when it consumes the4// approval: the two must be equal byte for byte. Each value has exactly one5// spelling, so equal actions always encode to equal strings.6//7// Fields are joined by "|":8//9// u:<uint64> decimal, no sign, no leading zero10// i:<int64> decimal, "-" only when negative, no leading zero11// b:0 or b:1 bool12// a:<address> the lowercase bech32 text the chain gives a caller13// s:<len>:<text> length-prefixed printable ASCII (0x20-0x7E)14//15// Strings are compared as raw bytes, with no Unicode normalisation; text is16// printable ASCII so that what a voter reads is exactly what executes.17package daoauth1819import (20 "errors"21 "strconv"22 "strings"23)2425const (26 MaxArgs = 1024 // bytes of one encoded argument string27 MaxText = 512 // bytes of one string field28)2930var (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)3637// Args accumulates fields in order. Methods panic on a value that has no38// canonical form, so a realm can never build an approval nobody can match.39type Args struct{ fields []string }4041func New() *Args { return &Args{} }4243func (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)) }4546func (a *Args) Bool(v bool) *Args {47 if v {48 return a.add("b:1")49 }50 return a.add("b:0")51}5253func (a *Args) Addr(v address) *Args {54 if !ValidAddress(v) {55 panic(ErrAddress.Error())56 }57 return a.add("a:" + string(v))58}5960func (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}6667func (a *Args) add(f string) *Args {68 a.fields = append(a.fields, f)69 return a70}7172// 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 s79}8081// Field is one decoded value: Tag is 'u', 'i', 'b', 'a' or 's', and Value82// is its text, without the length prefix for strings.83type Field struct {84 Tag byte85 Value string86}8788// Parse decodes s and refuses any text that is not the canonical encoding of89// 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, ErrLength93 }94 var out []Field95 for len(s) > 0 {96 if len(s) < 2 || s[1] != ':' {97 return nil, ErrSyntax98 }99 tag, rest := s[0], s[2:]100 var v string101 if tag == 's' {102 i := strings.IndexByte(rest, ':')103 if i < 1 {104 return nil, ErrSyntax105 }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, ErrSyntax109 }110 v, rest = rest[i+1:i+1+n], rest[i+1+n:]111 if !ValidText(v) {112 return nil, ErrText113 }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, err122 }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, ErrSyntax128 }129 rest = rest[1:]130 }131 s = rest132 }133 return out, nil134}135136func 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 ErrSyntax142 }143 case 'i':144 n, err := strconv.ParseInt(v, 10, 64)145 if err != nil || strconv.FormatInt(n, 10) != v {146 return ErrSyntax147 }148 case 'b':149 if v != "0" && v != "1" {150 return ErrSyntax151 }152 case 'a':153 if !ValidAddress(address(v)) {154 return ErrAddress155 }156 default:157 return ErrSyntax158 }159 return nil160}161162// ValidText reports whether s is at most MaxText bytes of printable ASCII163// (0x20 to 0x7E): no control, invisible, combining or direction-changing164// character can make a value read differently from what executes.165func ValidText(s string) bool {166 if len(s) > MaxText {167 return false168 }169 for i := 0; i < len(s); i++ {170 if s[i] < 0x20 || s[i] > 0x7e {171 return false172 }173 }174 return true175}176177// The chain decodes four texts to one address: the lowercase bech32 text it178// gives a caller, that text in capitals, and the bech32m checksum in either179// case. Only the first ever equals cur.Previous().Address(). The check below180// is the one of launchpad meta/v1, copied rather than imported so governance181// does not depend on the Launchpad release. It reads the text instead of182// decoding it.183184// bech32Value holds, for each character of the bech32 alphabet, its 5-bit185// 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}192193// 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}204205// bech32PrefixState is the checksum state after the prefix "g" (3, 0, 7).206const bech32PrefixState uint32 = 0x8c07207208// ValidAddress reports whether a is "g1" and 38 lowercase bech32 characters209// 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 false214 }215 chk := bech32PrefixState216 for i := 2; i < 40; i++ {217 v := bech32Value[s[i]]218 if v == 0 {219 return false220 }221 chk = (chk&0x1ffffff)<<5 ^ uint32(v-1) ^ bech32Gen[chk>>25]222 }223 return chk == 1224}225Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.