deadmanswitch.gno
3.94 Kb · 188 lines
1package deadmanswitch
2
3import (
4 "chain"
5 "chain/banker"
6 "chain/runtime"
7 "chain/runtime/unsafe"
8)
9
10type Status int
11
12const (
13 StatusActive Status = iota
14 StatusClaimed
15)
16
17func (s Status) String() string {
18 switch s {
19 case StatusActive:
20 return "Active"
21 case StatusClaimed:
22 return "Claimed"
23 default:
24 return "Unknown"
25 }
26}
27
28var (
29 initialized bool
30 owner address
31 beneficiary address
32 timeoutBlocks int64
33 lastHeartbeat int64
34 status Status
35)
36
37func Initialize(cur realm, ben address, timeout int64) {
38 if !cur.Previous().IsUserCall() {
39 panic("deadmanswitch: only direct user calls can initialize")
40 }
41 if initialized {
42 panic("deadmanswitch: already initialized")
43 }
44 if timeout <= 0 {
45 panic("deadmanswitch: timeoutBlocks must be positive")
46 }
47
48 caller := cur.Previous().Address()
49 if ben == caller {
50 panic("deadmanswitch: beneficiary cannot be the owner")
51 }
52 if ben == "" {
53 panic("deadmanswitch: beneficiary cannot be empty")
54 }
55
56 owner = caller
57 beneficiary = ben
58 timeoutBlocks = timeout
59 lastHeartbeat = runtime.ChainHeight()
60 status = StatusActive
61 initialized = true
62}
63
64func Deposit(cur realm) {
65 requireOwner(cur)
66
67 sent := unsafe.OriginSend()
68 amount := sent.AmountOf("ugnot")
69 if amount <= 0 {
70 panic("deadmanswitch: deposit must include a positive ugnot amount")
71 }
72
73 lastHeartbeat = runtime.ChainHeight()
74}
75
76func Heartbeat(cur realm) {
77 requireOwner(cur)
78 lastHeartbeat = runtime.ChainHeight()
79}
80
81func WithdrawOwner(cur realm, amount int64) {
82 requireOwner(cur)
83 if amount <= 0 {
84 panic("deadmanswitch: withdraw amount must be positive")
85 }
86
87 lastHeartbeat = runtime.ChainHeight()
88 payout(cur, owner, amount)
89}
90
91func ChangeBeneficiary(cur realm, newBeneficiary address) {
92 requireOwner(cur)
93 if newBeneficiary == owner {
94 panic("deadmanswitch: beneficiary cannot be the owner")
95 }
96 if newBeneficiary == "" {
97 panic("deadmanswitch: beneficiary cannot be empty")
98 }
99
100 beneficiary = newBeneficiary
101 lastHeartbeat = runtime.ChainHeight()
102}
103
104func ClaimAsBeneficiary(cur realm) {
105 if !initialized {
106 panic("deadmanswitch: not initialized yet")
107 }
108 if !cur.Previous().IsUserCall() {
109 panic("deadmanswitch: only direct user calls can claim")
110 }
111 if cur.Previous().Address() != beneficiary {
112 panic("deadmanswitch: caller is not the beneficiary")
113 }
114 if status != StatusActive {
115 panic("deadmanswitch: vault is not active (status=" + status.String() + ")")
116 }
117 if runtime.ChainHeight() < lastHeartbeat+timeoutBlocks {
118 panic("deadmanswitch: owner has not gone inactive long enough yet")
119 }
120
121 realmAddr := cur.Address()
122 b := banker.NewBanker(banker.BankerTypeRealmSend, cur)
123 balance := b.GetCoins(realmAddr).AmountOf("ugnot")
124
125 status = StatusClaimed
126
127 if balance > 0 {
128 b.SendCoins(realmAddr, beneficiary, chain.Coins{chain.NewCoin("ugnot", balance)})
129 }
130}
131
132func requireOwner(cur realm) {
133 if !initialized {
134 panic("deadmanswitch: not initialized yet")
135 }
136 if !cur.Previous().IsUserCall() {
137 panic("deadmanswitch: only direct user calls are accepted")
138 }
139 if cur.Previous().Address() != owner {
140 panic("deadmanswitch: caller is not the owner")
141 }
142 if status != StatusActive {
143 panic("deadmanswitch: vault is not active (status=" + status.String() + ")")
144 }
145}
146
147func payout(cur realm, to address, amount int64) {
148 realmAddr := cur.Address()
149 b := banker.NewBanker(banker.BankerTypeRealmSend, cur)
150
151 have := b.GetCoins(realmAddr).AmountOf("ugnot")
152 if have < amount {
153 panic("deadmanswitch: internal invariant violation, insufficient realm balance for payout")
154 }
155
156 b.SendCoins(realmAddr, to, chain.Coins{chain.NewCoin("ugnot", amount)})
157}
158
159func GetOwner() address {
160 return owner
161}
162
163func GetBeneficiary() address {
164 return beneficiary
165}
166
167func GetStatus() string {
168 if !initialized {
169 return "NotInitialized"
170 }
171 return status.String()
172}
173
174func GetLastHeartbeat() int64 {
175 return lastHeartbeat
176}
177
178func GetTimeoutBlocks() int64 {
179 return timeoutBlocks
180}
181
182func GetClaimableAtBlock() int64 {
183 return lastHeartbeat + timeoutBlocks
184}
185
186func IsInitialized() bool {
187 return initialized
188}