-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgov.py
443 lines (347 loc) · 17.3 KB
/
gov.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
# import dao
# import rewards
# import stamp_cost
import currency
# appease the linter
# import export
# import construct
# import ctx
# import Hash
# import now
# import datetime
Actions = Hash() # Actions, using action core pattern
Validators = Hash(default_value=0)
"""
Validators:<address>:active: bool
- If the validator can be included in the active set. False if validator has left.
Validators:<address>:locked: None or float
- The amount of tokens locked by the validator
Validators:<address>:unbonding: Date or None
- The date at which the validator can recover their locked validator tokens, False if they are not unbonding
Validators:<address>:power: float
- the voting power of the validator
Validators:<address>:commission: float
- the amount of commission that the validator takes from fees
Validators:<address>:epoch_joined: int
- The index of current_epoch + 1
Validators:<address>:epoch_collected: int or None
- The index of the last epoch which rewards were collected for.
"""
Delegators = Hash(default_value=0)
"""
Delegators:<address>:<validator>:amount: float
- The current amount of tokens delegated to the validator.
Delegators:<address>:<validator>:epoch_joined: int
- The point when the delegator joined the validator.
Delegators:<address>:<validator>:unbonding: Date or None
Delegators:<address>:<validator>:validator:p_record: Dict
- A record of changes in the delegation to this validator.
{
[epoch]: [amount]
}
"""
StakingEpochs = Hash() # Staking Epochs
Epoch_I = Variable() # Epoch Index - The index tracking the current epoch : int
TotalPower = Variable() # Total Power - The total voting power among all validators : float
ActivePower = Variable() # Active Power - The total voting power among all active validators : float
Rules = Hash() # This state is used to store the rules for the network. Alterable via governance votes.
"""
{
v_max: int, # The maximum number of validators in the active set.
v_lock: float, # The fee that validators take from rewards.
v_min_commission: float, # The minimum commission that validators can take.
fee_dist: list, # The distribution of txn fees between validators, black_hole, contract creators, and dao. e.g. [0.5, 0.2, 0.2, 0.1]
unbonding_period: int, # The days that validators must wait before they can recover their locked tokens.
epoch_length: int, # The number of hours in an epoch.
min_vote_turnout: float, # The minimum percentage of power that must vote on a proposal for it to be valid.
min_vote_ratio: float, # The minimum percentage of validators that must vote yes for a proposal to be valid.
}
"""
IssuanceRules = Hash(default_value=0) # IssuanceRules:rule_name: float
"""
{
staked_target: float, # The target % of circulating supply to be staked
reward_steepness: float, # The steepness of the reward curve
reward_min: float, # The minimum reward percentage
reward_max: float, # The maximum reward percentage
reward_target: float, # The target reward percentage
}
"""
# Votes = Hash(
# default_value=False
# ) # votes[] = {"y": bool, "n": bool, rule: str, arg: Any, "voters": list, "finalized": bool}
# Proposal_I = (
# Variable()
# ) # The index of the current proposal - when a proposal is created, the index is incremented.
DEFAULT_RULES = {
"v_max": 0,
"v_lock": 0.0,
"v_min_commission": 0.0,
"fee_dist": [0.0, 0.0, 0.0, 0.0],
"unbonding_period": 0,
"epoch_length": 0,
"min_vote_turnout": 0.0,
"min_vote_ratio": 0.0,
}
@construct
def seed(genesis_nodes: list, rules: dict = {}):
Rules["v_max"] = rules.get("v_max", DEFAULT_RULES["v_max"])
Rules["v_lock"] = rules.get("v_lock", DEFAULT_RULES["v_lock"])
Rules["v_min_commission"] = rules.get("v_min_commission", DEFAULT_RULES["v_min_commission"])
Rules["fee_dist"] = rules.get("fee_dist", DEFAULT_RULES["fee_dist"])
Rules["unbonding_period"] = rules.get("unbonding_period", DEFAULT_RULES["unbonding_period"])
Rules["epoch_length"] = rules.get("epoch_length", DEFAULT_RULES["epoch_length"])
Rules["min_vote_turnout"] = rules.get("min_vote_turnout", DEFAULT_RULES["min_vote_turnout"])
Rules["min_vote_ratio"] = rules.get("min_vote_ratio", DEFAULT_RULES["min_vote_ratio"])
Epoch_I.set(0)
TotalPower.set(0)
ActivePower.set(0)
for node in genesis_nodes:
Validators[node, 'active'] = True
Validators[node, "locked"] = Rules["v_lock"]
Validators[node, "unbonding"] = None
Validators[node, "power"] = Rules["v_lock"]
Validators[node, "commission"] = Rules["v_min_commission"]
Validators[node, "epoch_joined"] = 0
Validators[node, "epoch_collected"] = None
Validators[node, "is_genesis_node"] = True # Not returned tokens on leave.
TotalPower.set(TotalPower.get() + Rules["v_lock"])
StakingEpochs[0, node] = Rules["v_lock"]
Validators[node, "active"] = True
ActivePower.set(ActivePower.get() + Rules["v_lock"])
@export
def join(commission: float):
assert not Validators[ctx.caller, 'active'], "Already a validator"
join_fee = Rules["v_lock"]
assert currency.balance_of(ctx.caller) >= join_fee, "Insufficient funds to join"
min_commission = Rules["v_min_commission"]
assert commission >= min_commission, f"Commission must be at least {min_commission}"
currency.transfer_from(amount=join_fee, to=ctx.this, main_account=ctx.caller)
Validators[ctx.caller, 'active'] = True
Validators[ctx.caller, "locked"] = join_fee
Validators[ctx.caller, "unbonding"] = None
Validators[ctx.caller, "power"] += join_fee
Validators[ctx.caller, "commission"] = commission
Validators[ctx.caller, "epoch_joined"] = Epoch_I.get() + 1
Validators[ctx.caller, "epoch_collected"] = None
Validators[ctx.caller, "is_genesis_node"] = None
TotalPower.set(TotalPower.get() + join_fee)
def copy_from_hash(from_h, to_h, items: list):
dict_items = {}
for item in items:
from_value = from_h[*item]
to_h[*item] = from_value
def write_to_hash(h, items: dict):
keys = items.keys()
for k in keys:
h[k] = items[k]
@export
def announce_validator_leave():
assert Validators[ctx.caller, 'active'], "Not a validator"
assert not Validators[ctx.caller, "unbonding"], "Already unbonding"
Validators[ctx.caller, "unbonding"] = now + datetime.timedelta(
days=Rules["unbonding_period"]
)
@export
def cancel_validator_leave():
assert Validators[ctx.caller, 'active'], "Not an active validator"
assert Validators[ctx.caller, "unbonding"], "Not unbonding"
Validators[ctx.caller, "unbonding"] = None
@export
def validator_leave():
assert Validators[ctx.caller, 'active'], "Not a validator"
assert Validators[ctx.caller, "unbonding"], "Not unbonding"
assert Validators[ctx.caller, "unbonding"] <= now, "Unbonding period not over"
# perform the transfer
if not Validators[ctx.caller, "is_genesis_node"]:
currency.transfer(Validators[ctx.caller, "locked"], ctx.caller)
# reset the validator record.
Validators[ctx.caller, 'active'] = False
Validators[ctx.caller, "unbonding"] = None
Validators[ctx.caller, "power"] -= Validators[ctx.caller, "locked"]
Validators[ctx.caller, "locked"] = None
Validators[ctx.caller, "is_genesis_node"] = None
TotalPower.set(TotalPower.get() - Validators[ctx.caller, "locked"])
@export
def delegate(validator: str, amount: float):
"""
Called by : Delegator
* Delegates tokens to a validator.
* Increases the voting power of the validator.
* Cannot delegate to a validator that is unbonding.
* Cannot delegate to a validator if caller has a delegation to validator that is unbonding.
* Value must be greater than 0.
"""
assert amount > 0, "Amount must be greater than 0"
assert Validators[validator, 'active'], "Validator is not registered"
assert not Validators[validator, "unbonding"], "Validator is unbonding"
assert not Delegators[ctx.caller, validator, "unbonding"], "This delegation is unbonding, please cancel the unbonding period first"
currency_balances = ForeignHash(foreign_contract="currency", foreign_name="balances")
assert currency_balances[ctx.caller] >= amount, "Insufficient funds"
assert currency_balances[ctx.caller, ctx.this] >= amount, "Insufficient allowance"
currency.transfer_from(amount=amount, to=ctx.this, main_account=ctx.caller)
Delegators[ctx.caller, validator, "amount"] += amount
Delegators[ctx.caller, validator, "epoch_joined"] = Epoch_I.get() + 1
Delegators[ctx.caller, validator, "unbonding"] = None
Delegators[ctx.caller, validator, "record"] = {
Epoch_I.get() + 1: amount
}
Validators[validator, "power"] += amount
TotalPower.set(TotalPower.get() + amount)
@export
def announce_delegator_leave(validator: str):
"""
Called by : Delegator
* Immediately removes delegated voting power from a validator.
* If the validator is unbonding, the delegated tokens are locked until the validator unbonding period is over.
* If the validator is not unbonding, the delegated tokens can be claimed after the standard unbonding period, defined in Rules.
* If the validator is no longer registered, the delegated tokens can be claimed immediately / unbonding period set to now.
"""
assert Delegators[ctx.caller, validator, "amount"] > 0, "No delegation to leave"
assert not Delegators[ctx.caller, validator, "unbonding"], "Already unbonding"
# Validator has left the network
if not Validators[validator, 'active']:
currency.transfer(Delegators[ctx.caller, validator, "amount"], ctx.caller)
Validators[validator, "power"] -= Delegators[ctx.caller, validator, "amount"]
Delegators[ctx.caller, validator, "amount"] = 0
Delegators[ctx.caller, validator, "record"] = None
TotalPower.set(TotalPower.get() - Delegators[ctx.caller, validator, "amount"])
return
# Validator is unbonding
elif Validators[validator, "unbonding"]:
Delegators[ctx.caller, validator, "unbonding"] = Validators[validator, "unbonding"]
Validators[validator, "power"] -= Delegators[ctx.caller, validator, "amount"]
# Validator is not unbonding
else:
Delegators[ctx.caller, validator, "unbonding"] = now + datetime.timedelta(days=Rules["unbonding_period"])
Validators[validator, "power"] -= Delegators[ctx.caller, validator, "amount"]
Delegators[ctx.caller, validator, 'record'][Epoch_I.get()] = 0
TotalPower.set(TotalPower.get() - Delegators[ctx.caller, validator, "amount"])
@export
def cancel_delegator_leave(validator: str):
"""
Called by : Delegator
* Cancels the unbonding period for a delegation.
"""
assert Delegators[ctx.caller, validator, 'amount'] > 0, "No delegation to leave"
assert Delegators[ctx.caller, validator, "unbonding"], "Not unbonding"
Delegators[ctx.caller, validator, "unbonding"] = None
Delegators[ctx.caller, validator, "record"][Epoch_I.get() + 1] = Delegators[ctx.caller, validator, "amount"]
Validators[validator, "power"] += Delegators[ctx.caller, validator, "amount"]
@export
def redelegate(from_validator: str, to_validator: str, amount: float):
"""
Called by : Delegator
* Moves tokens delegated from one validator to another.
* Can be performed when there is a delegation to the from_validator.
* Can be performed when the to_validator is not unbonding and is active.
* Cannot be performed when the delegator is unbonding from the validator.
* Cannot be performed when the delegator is unbonding from the to_validator.
"""
# Validator Checks
assert Validators[to_validator, 'active'], "To validator is not active"
assert not Validators[to_validator, "unbonding"], "To validator is unbonding"
# Delegator Checks
assert Delegators[ctx.caller, from_validator, 'amount'] > 0, "No delegation to move"
assert Delegators[ctx.caller, from_validator, 'amount'] >= amount, "Insufficient delegation"
assert not Delegators[ctx.caller, from_validator, "unbonding"], "The 'from' delegation is unbonding, cancel the unbonding first"
assert not Delegators[ctx.caller, to_validator, "unbonding"], "The 'to' delegation is unbonding, cancel the unbonding first"
Delegators[ctx.caller, from_validator, "amount"] -= amount
Delegators[ctx.caller, from_validator, 'record'][Epoch_I.get()] -= amount
Delegators[ctx.caller, to_validator, "amount"] += amount
Delegators[ctx.caller, to_validator, 'record'][Epoch_I.get() + 1] += amount
Validators[from_validator, "power"] -= amount
Validators[to_validator, "power"] += amount
@export
def delegator_leave(validator: str):
"""
Called by : Delegator
* Can be called after the unbonding period
"""
assert Delegators[ctx.caller, validator, 'amount'] > 0, "No delegation to leave"
assert Delegators[ctx.caller, validator, "unbonding"], 'Not unbonding, call announce_delegator_leave first'
assert Delegators[ctx.caller, validator, "unbonding"] <= now, 'Unbonding period not over'
currency.transfer(Delegators[ctx.caller, validator, "amount"], ctx.caller)
Delegators[ctx.caller, validator, "amount"] = 0
Delegators[ctx.caller, validator, "unbonding"] = None
Delegators[ctx.caller, validator, "validator"] = None
# @export
# def propose(type_of_vote: str, arg: Any):
# assert ctx.caller in VA.get(), "Only nodes can propose new votes"
# assert type_of_vote in types.get(), "Invalid type"
# proposal_id = Proposal_I.get() + 1
# Proposal_I.set(proposal_id)
# votes[proposal_id][] = {"yes": 1, "no": 0, "type": type_of_vote, "arg": arg, "voters": [ctx.caller], "finalized": False}
# total_votes.set(proposal_id)
# if len(votes[proposal_id]["voters"]) >= len(nodes.get()) // 2: # Single node network edge case
# if not votes[proposal_id]["finalized"]:
# finalize_vote(proposal_id)
# return proposal_id
# @export
# def vote(proposal_id: int, vote: str):
# assert ctx.caller in nodes.get(), "Only nodes can vote"
# assert votes[proposal_id], "Invalid proposal"
# assert votes[proposal_id]["finalized"] == False, "Proposal already finalized"
# assert vote in ["Y", "N"], "Invalid vote"
# assert ctx.caller not in votes[proposal_id]["voters"], "Already voted"
# # Do this because we can't modify a dict in a hash without reassigning it
# cur_vote = votes[proposal_id]
# cur_vote[vote] += 1
# cur_vote["voters"].append(ctx.caller)
# votes[proposal_id] = cur_vote
# if len(votes[proposal_id]["voters"]) >= len(nodes.get()) // 2:
# if not votes[proposal_id]["finalized"]:
# finalize_vote(proposal_id)
# return cur_vote
# def finalize_vote(proposal_id: int):
# cur_vote = votes[proposal_id]
# # Check if majority yes
# if cur_vote["yes"] > cur_vote["no"]:
# if cur_vote["type"] == "add_member":
# nodes.set(nodes.get() + [cur_vote["arg"]])
# elif cur_vote["type"] == "remove_member":
# nodes.set([node for node in nodes.get() if node != cur_vote["arg"]])
# force_leave(cur_vote["arg"])
# elif cur_vote["type"] == "reward_change":
# rewards.set_value(new_value=cur_vote["arg"])
# elif cur_vote["type"] == "dao_payout":
# dao.transfer_from_dao(args=cur_vote["arg"])
# elif cur_vote["type"] == "stamp_cost_change":
# stamp_cost.set_value(new_value=cur_vote["arg"])
# elif cur_vote["type"] == "change_registration_fee":
# registration_fee.set(cur_vote["arg"])
# elif cur_vote["type"] == "change_types":
# types.set(cur_vote["arg"])
# cur_vote["finalized"] = True
# votes[proposal_id] = cur_vote
# return cur_vote
# def force_leave(node: str):
# pending_leave[node] = now + datetime.timedelta(days=7)
# @export
# def announce_leave():
# assert ctx.caller in nodes.get(), "Not a node"
# assert pending_leave[ctx.caller] == False, "Already pending leave"
# pending_leave[ctx.caller] = now + datetime.timedelta(days=7)
# @export
# def slash
# @export
# def leave():
# assert pending_leave[ctx.caller] < now, "Leave announcement period not over"
# if ctx.caller in nodes.get():
# nodes.set([node for node in nodes.get() if node != ctx.caller])
# pending_leave[ctx.caller] = False
# # @export
# # def register():
# # assert ctx.caller not in nodes.get(), "Already a node"
# # assert pending_registrations[ctx.caller] == False, "Already pending registration"
# # currency.transfer_from(amount=registration_fee.get(), to=ctx.this, main_account=ctx.caller)
# # holdings[ctx.caller] = registration_fee.get()
# # pending_registrations[ctx.caller] = True
# # @export
# # def unregister():
# # assert ctx.caller not in nodes.get(), "If you're a node already, you can't unregister. You need to leave or be removed."
# # assert pending_registrations[ctx.caller] == True, "No pending registration"
# # if holdings[ctx.caller] > 0:
# # currency.transfer(holdings[ctx.caller], ctx.caller)
# # pending_registrations[ctx.caller] = False
# # holdings[ctx.caller] = 0