/ test / contracts / MainStaking.aes
MainStaking.aes
  1  include "List.aes"
  2  include "Pair.aes"
  3  include "StakingValidator.aes"
  4  
  5  contract interface StakingValidatorI =
  6    entrypoint rewards : (int, int, bool) => unit
  7  
  8  main contract MainStaking =
  9    record validator =
 10      { owner         : address,
 11        sign_key      : address,
 12        total_balance : int,
 13        current_stake : int,
 14        staked        : map(int, int),
 15        restake       : bool
 16      }
 17  
 18    record state =
 19      { validators          : map(address, validator),
 20        owners              : map(address, address),
 21        sign_keys           : map(address, address),
 22        validator_min_stake : int,
 23        penalty_pool        : int,
 24        current_epoch       : int
 25      }
 26  
 27    entrypoint init(validator_min_stake : int) =
 28      { validators = {},
 29        owners = {},
 30        sign_keys = {},
 31        validator_min_stake = validator_min_stake,
 32        penalty_pool = 0,
 33        // The first block is part of epoch 1
 34        current_epoch = 1 }
 35  
 36    payable stateful entrypoint new_validator(owner : address, sign_key : address, restake : bool) : StakingValidator =
 37      require(Call.value >= state.validator_min_stake, "A new validator must stake the minimum amount")
 38      require(!Map.member(owner, state.owners), "Owner must be unique")
 39      require(!Map.member(sign_key, state.sign_keys), "Sign key must be unique")
 40      let validator_ct = Chain.create(Address.to_contract(Contract.address), owner, sign_key) : StakingValidator
 41      let v_addr = validator_ct.address
 42      put(state{validators[v_addr] = {owner = owner, sign_key = sign_key,
 43                                      total_balance = 0, current_stake = 0,
 44                                      staked = {}, restake = restake},
 45                owners[owner] = v_addr,
 46                sign_keys[sign_key] = v_addr})
 47      stake_(v_addr, Call.value)
 48      validator_ct
 49  
 50    // ------------------------------------------------------------------------
 51    // -- StakingValidator API
 52    // ------------------------------------------------------------------------
 53    payable stateful entrypoint deposit() =
 54      require(Call.value > 0, "Deposit needs a positive value")
 55      assert_validator(Call.caller)
 56      deposit_(Call.caller, Call.value)
 57  
 58    payable stateful entrypoint stake() =
 59      require(Call.value > 0, "Stake needs a positive value")
 60      assert_validator(Call.caller)
 61      stake_(Call.caller, Call.value)
 62  
 63    stateful entrypoint adjust_stake(amount : int) =
 64      assert_validator(Call.caller)
 65      adjust_stake_(Call.caller, amount)
 66  
 67    stateful entrypoint withdraw(amount) =
 68      assert_validator(Call.caller)
 69      let available = get_available_balance_(Call.caller)
 70      require(available >= amount, "Too large withdrawal")
 71  
 72      withdraw_(Call.caller, amount)
 73      Chain.spend(Call.caller, amount)
 74  
 75    stateful entrypoint set_restake(restake : bool) =
 76      assert_validator(Call.caller)
 77      put(state{validators[Call.caller] @ v = v{restake = restake}})
 78  
 79    entrypoint get_restake() : bool =
 80      state.validators[Call.caller].restake
 81  
 82    entrypoint get_staked_amount(epoch : int) =
 83      get_staked_amount_(Call.caller, epoch)
 84  
 85    entrypoint get_current_stake() =
 86      get_current_stake_(Call.caller)
 87  
 88    entrypoint get_current_stake_(validator : address) =
 89      let v = state.validators[validator]
 90      v.current_stake
 91  
 92    entrypoint get_available_balance() =
 93      get_available_balance_(Call.caller)
 94  
 95    entrypoint get_available_balance_(validator : address) : int =
 96      let v = state.validators[validator]
 97      v.total_balance - locked_stake(v)
 98  
 99    entrypoint get_total_balance() =
100      get_total_balance_(Call.caller)
101  
102    entrypoint get_total_balance_(v : address) =
103      state.validators[v].total_balance
104  
105    entrypoint get_validator_min_stake() =
106      state.validator_min_stake
107  
108    // ------------------------------------------------------------------------
109    // -- Called from HCElection and/or consensus logic
110    // ------------------------------------------------------------------------
111    payable stateful entrypoint add_rewards(epoch : int, rewards : list(address * int)) =
112      assert_protocol_call()
113      let total_rewards = List.foldl((+), 0, List.map(Pair.snd, rewards))
114      require(total_rewards == Call.value, "Incorrect total reward given")
115      List.foreach(rewards, (r) => add_reward(epoch, r))
116      [ unlock_stake_(v_addr, validator, epoch) | (v_addr, validator) <- Map.to_list(state.validators) ]
117      // At the end of epoch X we distribute rewards for X - 1; thus current_epoch
118      // is (soon) X + 1. I.e. X - 1 + 2.
119      put(state{current_epoch = epoch + 2})
120  
121    stateful entrypoint add_penalties(epoch : int, penalties : list(address * int)) =
122      assert_protocol_call()
123      List.foreach(penalties, (p) => add_penalty(epoch, p))
124  
125    stateful entrypoint lock_stake(epoch : int) : list(address * int) =
126      assert_protocol_call()
127      [ lock_stake_(v_addr, validator, epoch) | (v_addr, validator) <- Map.to_list(state.validators) ]
128      sorted_validators()
129  
130    entrypoint sorted_validators() : list(address * int) =
131      let vs = [ (sk, s) | (_, {sign_key = sk, current_stake = s}) <- Map.to_list(state.validators),
132                           if(s >= state.validator_min_stake) ]
133  
134      List.sort(cmp_validator, vs)
135  
136    // ------------------------------------------------------------------------
137    // -- Lookup API
138    // ------------------------------------------------------------------------
139    entrypoint staking_power(owner : address) =
140      let v = lookup_validator(owner)
141      v.current_stake
142  
143    entrypoint get_validator_state(owner : address) =
144      lookup_validator(owner)
145  
146    entrypoint get_validator_contract(owner : address) : StakingValidator =
147      assert_owner(owner)
148      Address.to_contract(state.owners[owner])
149  
150    entrypoint get_current_epoch() =
151      state.current_epoch
152  
153    entrypoint get_penalty_pool() =
154      state.penalty_pool
155  
156    // ------------------------------------------------------------------------
157    // --   Internal functions
158    // ------------------------------------------------------------------------
159    function cmp_validator((x_addr : address, x_stake : int), (y_addr : address, y_stake : int)) =
160      if (x_stake == y_stake) x_addr < y_addr else x_stake > y_stake
161  
162    function lookup_validator(owner : address) =
163      assert_owner(owner)
164      state.validators[state.owners[owner]]
165  
166    stateful function add_reward(epoch : int, (sign_key, amount) : address * int) =
167      assert_signer(sign_key)
168      let validator = state.sign_keys[sign_key]
169      let restake = state.validators[validator].restake
170      if(restake)
171        stake_(validator, amount)
172      else
173        deposit_(validator, amount)
174      let validator_ct = Address.to_contract(validator) : StakingValidator
175      validator_ct.rewards(epoch, amount, restake)
176  
177    // an penalty will be a positive amount, a redistribution of penalty (reporting reward or other)
178    // will be a negative amount
179    stateful function add_penalty(epoch : int, (sign_key, amount) : address * int) =
180      assert_signer(sign_key)
181      if (amount < 0)
182        require(state.penalty_pool >= -amount, "Not enough penalty pool")
183      let validator_key = state.sign_keys[sign_key]
184      let validator = state.validators[validator_key]
185      let total_balance = validator.total_balance
186      let current_stake = validator.current_stake
187      let new_total = max([0, total_balance - amount])
188      let new_cur_stake = min(current_stake, new_total)
189      put(state{validators[validator_key] @ v = v{total_balance = new_total,
190                                              current_stake = new_cur_stake},
191                penalty_pool = state.penalty_pool + amount})
192  
193    stateful function lock_stake_(v_addr : address, validator : validator, epoch : int) : unit =
194      if(validator.current_stake >= state.validator_min_stake)
195        put(state{validators[v_addr] = validator{staked @ s = s{[epoch] = validator.current_stake}}})
196  
197    stateful function unlock_stake_(v_addr : address, validator : validator, epoch : int) : unit =
198      put(state{validators[v_addr] = validator{staked @ s = Map.delete(epoch, s)}})
199  
200    stateful function deposit_(validator : address, amount : int) =
201      put(state{validators[validator] @ v = deposit_v(v, amount)})
202  
203    stateful function stake_(validator : address, amount : int) =
204      put(state{validators[validator] @ v = stake_v(v, amount)})
205  
206    stateful function withdraw_(validator : address, amount : int) =
207      put(state{validators[validator] @ v = withdraw_v(v, amount)})
208  
209    function get_staked_amount_(validator : address, epoch : int) =
210      Map.lookup_default(epoch, state.validators[validator].staked, 0)
211  
212    stateful function adjust_stake_(validator : address, amount : int) =
213      put(state{validators[validator] @ v = adjust_stake_v(v, amount)})
214  
215    function deposit_v(v : validator, amount) =
216      v{total_balance @ tb = tb + amount}
217  
218    function stake_v(v : validator, amount) =
219      v{total_balance @ tb = tb + amount,
220        current_stake @ cs = cs + amount}
221  
222    function withdraw_v(v : validator, amount) =
223      let total_balance = v.total_balance - amount
224      v{total_balance = total_balance,
225        current_stake @ cs = min(total_balance, cs) }
226  
227    function adjust_stake_v(v : validator, amount) =
228      require(v.total_balance >= v.current_stake + amount, "Too large stake")
229      require(0 =< v.current_stake + amount, "Too small stake")
230      v{current_stake @ cs = cs + amount}
231  
232    function locked_stake(v : validator) =
233      let stakes = List.map(Pair.snd, Map.to_list(v.staked))
234      max(stakes)
235  
236    function max(ls : list(int)) : int =
237      List.foldl((a, b) => if(a > b) a else b, 0, ls)
238  
239    function min(a : int, b : int) =
240      if(a < b) a else b
241  
242    function assert_validator(v : address) =
243      require(Map.member(v, state.validators), "Not a registered validator")
244  
245    function assert_owner(o : address) =
246      require(Map.member(o, state.owners), "Not a registered validator owner")
247  
248    function assert_signer(s : address) =
249      require(Map.member(s, state.sign_keys), "Not a registered sign key")
250  
251    function assert_protocol_call() =
252      require(Call.origin == Contract.creator, "Must be called by the protocol")