allocations.gno
2.43 Kb · 92 lines
1package sales
2
3import (
4 "strconv"
5 "strings"
6
7 "chain"
8 "gno.land/p/samcrew/launchpad/meta/v1"
9 sm "gno.land/p/samcrew/launchpad/safemath/v1"
10 "gno.land/p/samcrew/launchpad/vesting/v1"
11)
12
13const (
14 maxAllocationBytes = 8192
15 maxAllocations = 50
16)
17
18type allocation struct {
19 beneficiary address
20 amount int64
21 start int64
22 cliff int64
23 duration int64
24 revocable bool
25}
26
27func decimal(s string) int64 {
28 if s == "" || (len(s) > 1 && s[0] == '0') {
29 panic("launchpad/sales: noncanonical allocation decimal")
30 }
31 for i := 0; i < len(s); i++ {
32 if s[i] < '0' || s[i] > '9' {
33 panic("launchpad/sales: noncanonical allocation decimal")
34 }
35 }
36 v, err := strconv.ParseInt(s, 10, 64)
37 if err != nil {
38 panic("launchpad/sales: allocation decimal out of range")
39 }
40 return v
41}
42
43// parseAllocations accepts a single bounded, canonical MsgCall argument.
44// Each record is address:amount:start:cliff:duration:revocable, with the
45// address order providing both a stable encoding and duplicate rejection.
46func parseAllocations(encoded string) ([]allocation, int64) {
47 if len(encoded) > maxAllocationBytes {
48 panic("launchpad/sales: allocations too large")
49 }
50 if encoded == "" {
51 return nil, 0
52 }
53 records := strings.Split(encoded, ";")
54 if len(records) > maxAllocations {
55 panic("launchpad/sales: too many allocations")
56 }
57 out := make([]allocation, 0, len(records))
58 var total int64
59 previous := ""
60 for _, record := range records {
61 fields := strings.Split(record, ":")
62 if len(fields) != 6 || fields[0] <= previous {
63 panic("launchpad/sales: invalid allocation order or fields")
64 }
65 who := address(fields[0])
66 if !meta.ValidAddress(who) || who == chain.PackageAddress(salesPath) {
67 panic("launchpad/sales: invalid allocation address")
68 }
69 a := allocation{
70 beneficiary: who,
71 amount: decimal(fields[1]),
72 start: decimal(fields[2]),
73 cliff: decimal(fields[3]),
74 duration: decimal(fields[4]),
75 }
76 if a.amount == 0 || (fields[5] != "0" && fields[5] != "1") {
77 panic("launchpad/sales: invalid allocation amount or revocability")
78 }
79 a.revocable = fields[5] == "1"
80 if a.duration == 0 {
81 if a.start != 0 || a.cliff != 0 || a.revocable {
82 panic("launchpad/sales: invalid immediate allocation")
83 }
84 } else {
85 vesting.Schedule{Total: a.amount, Start: a.start, Cliff: a.cliff, Duration: a.duration}.Validate()
86 }
87 total = sm.Add(total, a.amount)
88 out = append(out, a)
89 previous = fields[0]
90 }
91 return out, total
92}