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")