deadmanswitch.gno
5.17 Kb · 256 lines
1package deadmanswitch
2
3type SwitchStatus int
4
5const (
6 SwitchActive SwitchStatus = iota
7 SwitchTriggered
8 SwitchClaimed
9 SwitchCancelled
10)
11
12type Beneficiary struct {
13 Addr string
14 SharePct int64 // out of 100
15}
16
17type Switch struct {
18 ID string
19 Owner string
20 LastCheckin int64
21 TimeoutBlocks int64
22 Balance int64
23 Beneficiaries []Beneficiary
24 Status SwitchStatus
25 claimed map[string]bool
26}
27
28type Registry struct {
29 admin string
30 switches map[string]*Switch
31 nextSeq int64
32 currentHeight int64
33}
34
35func NewRegistry(admin string) *Registry {
36 return &Registry{admin: admin, switches: make(map[string]*Switch)}
37}
38
39func (r *Registry) Tick(height int64) {
40 if height > r.currentHeight {
41 r.currentHeight = height
42 }
43}
44
45func (r *Registry) CreateSwitch(owner string, timeoutBlocks int64) (string, bool) {
46 if owner == "" || timeoutBlocks <= 0 {
47 return "", false
48 }
49 r.nextSeq++
50 id := itoa(r.nextSeq)
51 r.switches[id] = &Switch{
52 ID: id,
53 Owner: owner,
54 LastCheckin: r.currentHeight,
55 TimeoutBlocks: timeoutBlocks,
56 Status: SwitchActive,
57 claimed: make(map[string]bool),
58 }
59 return id, true
60}
61
62func (r *Registry) SetBeneficiaries(caller, switchID string, addrs []string, pcts []int64) bool {
63 s, ok := r.switches[switchID]
64 if !ok || s.Status != SwitchActive || caller != s.Owner {
65 return false
66 }
67 if len(addrs) != len(pcts) || len(addrs) == 0 {
68 return false
69 }
70 var total int64
71 for _, p := range pcts {
72 if p <= 0 {
73 return false
74 }
75 total += p
76 }
77 if total != 100 {
78 return false
79 }
80 bens := make([]Beneficiary, len(addrs))
81 for i, a := range addrs {
82 bens[i] = Beneficiary{Addr: a, SharePct: pcts[i]}
83 }
84 s.Beneficiaries = bens
85 return true
86}
87
88func (r *Registry) Deposit(switchID string, amount int64) bool {
89 s, ok := r.switches[switchID]
90 if !ok || s.Status != SwitchActive || amount <= 0 {
91 return false
92 }
93 s.Balance += amount
94 return true
95}
96
97func (r *Registry) Checkin(caller, switchID string) bool {
98 s, ok := r.switches[switchID]
99 if !ok || s.Status != SwitchActive || caller != s.Owner {
100 return false
101 }
102 s.LastCheckin = r.currentHeight
103 return true
104}
105
106func (r *Registry) Trigger(switchID string) bool {
107 s, ok := r.switches[switchID]
108 if !ok || s.Status != SwitchActive {
109 return false
110 }
111 if r.currentHeight-s.LastCheckin < s.TimeoutBlocks {
112 return false
113 }
114 if len(s.Beneficiaries) == 0 {
115 return false
116 }
117 s.Status = SwitchTriggered
118 return true
119}
120
121func (r *Registry) ClaimShare(caller, switchID string) int64 {
122 s, ok := r.switches[switchID]
123 if !ok || s.Status != SwitchTriggered {
124 return 0
125 }
126 if s.claimed[caller] {
127 return 0
128 }
129 var pct int64 = -1
130 for _, b := range s.Beneficiaries {
131 if b.Addr == caller {
132 pct = b.SharePct
133 break
134 }
135 }
136 if pct < 0 {
137 return 0
138 }
139 share := (s.Balance * pct) / 100
140 s.claimed[caller] = true
141 allClaimed := true
142 for _, b := range s.Beneficiaries {
143 if !s.claimed[b.Addr] {
144 allClaimed = false
145 break
146 }
147 }
148 if allClaimed {
149 s.Status = SwitchClaimed
150 }
151 return share
152}
153
154func (r *Registry) Cancel(caller, switchID string) bool {
155 s, ok := r.switches[switchID]
156 if !ok || caller != s.Owner || s.Status != SwitchActive {
157 return false
158 }
159 s.Status = SwitchCancelled
160 return true
161}
162
163func (r *Registry) GetSwitch(id string) (*Switch, bool) {
164 s, ok := r.switches[id]
165 return s, ok
166}
167
168func (r *Registry) BlocksUntilTrigger(switchID string) int64 {
169 s, ok := r.switches[switchID]
170 if !ok || s.Status != SwitchActive {
171 return -1
172 }
173 elapsed := r.currentHeight - s.LastCheckin
174 remaining := s.TimeoutBlocks - elapsed
175 if remaining < 0 {
176 return 0
177 }
178 return remaining
179}
180
181func itoa(n int64) string {
182 if n == 0 {
183 return "0"
184 }
185 neg := n < 0
186 if neg {
187 n = -n
188 }
189 var buf [20]byte
190 i := len(buf)
191 for n > 0 {
192 i--
193 buf[i] = byte('0' + n%10)
194 n /= 10
195 }
196 s := string(buf[i:])
197 if neg {
198 s = "-" + s
199 }
200 return s
201}
202
203// package-level singleton + exported entry points for the realm
204
205var registry = NewRegistry("g16m0r7rm7fv5hx0ekr7gvx8g4fr7eu08nzhk6gt")
206
207func Tick(height int64) {
208 registry.Tick(height)
209}
210
211func CreateSwitch(owner string, timeoutBlocks int64) string {
212 id, ok := registry.CreateSwitch(owner, timeoutBlocks)
213 if !ok {
214 return ""
215 }
216 return id
217}
218
219func SetBeneficiaries(caller, switchID string, addrs []string, pcts []int64) bool {
220 return registry.SetBeneficiaries(caller, switchID, addrs, pcts)
221}
222
223func Deposit(switchID string, amount int64) bool {
224 return registry.Deposit(switchID, amount)
225}
226
227func Checkin(caller, switchID string) bool {
228 return registry.Checkin(caller, switchID)
229}
230
231func Trigger(switchID string) bool {
232 return registry.Trigger(switchID)
233}
234
235func ClaimShare(caller, switchID string) int64 {
236 return registry.ClaimShare(caller, switchID)
237}
238
239func Cancel(caller, switchID string) bool {
240 return registry.Cancel(caller, switchID)
241}
242
243func BlocksUntilTrigger(switchID string) int64 {
244 return registry.BlocksUntilTrigger(switchID)
245}
246
247func Render(path string) string {
248 if path == "" {
249 return "DeadManSwitch realm — use CreateSwitch, Checkin, Trigger, ClaimShare"
250 }
251 s, ok := registry.GetSwitch(path)
252 if !ok {
253 return "switch not found: " + path
254 }
255 return "Owner: " + s.Owner + " | Balance: " + itoa(s.Balance) + " | Beneficiaries: " + itoa(int64(len(s.Beneficiaries)))
256}