github.com/Onther-Tech/plasma-evm@v0.0.0-rc7.7/contracts/stamina/contract/Stamina.sol (about) 1 pragma solidity ^0.5.12; 2 3 4 contract Stamina { 5 struct Withdrawal { 6 uint128 amount; 7 uint128 requestBlockNumber; 8 address delegatee; 9 bool processed; 10 } 11 12 /** 13 * Internal States 14 */ 15 // delegatee of `delegator` account 16 // `delegator` => `delegatee` 17 mapping (address => address) _delegatee; 18 19 // stamina of delegatee 20 // `delegatee` => `stamina` 21 mapping (address => uint) _stamina; 22 23 // total deposit of delegatee 24 // `delegatee` => `total deposit` 25 mapping (address => uint) _total_deposit; 26 27 // deposit of delegatee 28 // `depositor` => `delegatee` => `deposit` 29 mapping (address => mapping (address => uint)) _deposit; 30 31 // last recovery block of delegatee 32 mapping (address => uint256) _last_recovery_block; 33 34 // depositor => [index] => Withdrawal 35 mapping (address => Withdrawal[]) _withdrawal; 36 mapping (address => uint256) _last_processed_withdrawal; 37 mapping (address => uint) _num_recovery; 38 39 /** 40 * Public States 41 */ 42 bool public initialized; 43 44 uint public MIN_DEPOSIT; 45 uint public RECOVER_EPOCH_LENGTH; // stamina is recovered when block number % RECOVER_DELAY == 0 46 uint public WITHDRAWAL_DELAY; // Refund will be made WITHDRAWAL_DELAY blocks after depositor request Withdrawal. 47 // WITHDRAWAL_DELAY prevents immediate withdrawal. 48 // RECOVER_EPOCH_LENGTH * 2 < WITHDRAWAL_DELAY 49 50 51 bool public development = true; // if the contract is inserted directly into 52 // genesis block without state, it will be false 53 54 /** 55 * Modifiers 56 */ 57 modifier onlyChain() { 58 require(development || msg.sender == address(0)); 59 _; 60 } 61 62 modifier onlyInitialized() { 63 require(initialized); 64 _; 65 } 66 67 /** 68 * Events 69 */ 70 event Deposited(address indexed depositor, address indexed delegatee, uint amount); 71 event DelegateeChanged(address indexed delegator, address oldDelegatee, address newDelegatee); 72 event WithdrawalRequested(address indexed depositor, address indexed delegatee, uint amount, uint requestBlockNumber, uint withdrawalIndex); 73 event Withdrawn(address indexed depositor, address indexed delegatee, uint amount, uint withdrawalIndex); 74 event StaminaAdded(address indexed delegatee, uint amount, bool recovered); 75 event StaminaSubtracted(address indexed delegatee, uint amount); 76 77 /** 78 * Init 79 */ 80 function init(uint minDeposit, uint recoveryEpochLength, uint withdrawalDelay) external { 81 require(!initialized); 82 83 require(minDeposit > 0); 84 require(recoveryEpochLength > 0); 85 require(withdrawalDelay > 0); 86 87 require(recoveryEpochLength * 2 < withdrawalDelay); 88 89 MIN_DEPOSIT = minDeposit; 90 RECOVER_EPOCH_LENGTH = recoveryEpochLength; 91 WITHDRAWAL_DELAY = withdrawalDelay; 92 93 initialized = true; 94 } 95 96 /** 97 * Getters 98 */ 99 function getDelegatee(address delegator) public view returns (address) { 100 return _delegatee[delegator]; 101 } 102 103 function getStamina(address addr) public view returns (uint) { 104 return _stamina[addr]; 105 } 106 107 function getTotalDeposit(address delegatee) public view returns (uint) { 108 return _total_deposit[delegatee]; 109 } 110 111 function getDeposit(address depositor, address delegatee) public view returns (uint) { 112 return _deposit[depositor][delegatee]; 113 } 114 115 function getNumWithdrawals(address depositor) public view returns (uint) { 116 return _withdrawal[depositor].length; 117 } 118 119 function getLastRecoveryBlock(address delegatee) public view returns (uint) { 120 return _last_recovery_block[delegatee]; 121 } 122 123 function getNumRecovery(address delegatee) public view returns (uint) { 124 return _num_recovery[delegatee]; 125 } 126 127 function getLastProcessedWithdrawalIndex(address depositor) public view returns (uint) { 128 return _last_processed_withdrawal[depositor]; 129 } 130 131 function getWithdrawal(address depositor, uint withdrawalIndex) 132 public 133 view 134 returns (uint128 amount, uint128 requestBlockNumber, address delegatee, bool processed) 135 { 136 require(withdrawalIndex < getNumWithdrawals(depositor)); 137 138 Withdrawal memory w = _withdrawal[depositor][withdrawalIndex]; 139 140 amount = w.amount; 141 requestBlockNumber = w.requestBlockNumber; 142 delegatee = w.delegatee; 143 processed = w.processed; 144 } 145 146 /** 147 * Setters 148 */ 149 /// @notice Set `msg.sender` as delegatee of `delegator` 150 function setDelegator(address delegator) 151 external 152 onlyInitialized 153 returns (bool) 154 { 155 address oldDelegatee = _delegatee[delegator]; 156 157 _delegatee[delegator] = msg.sender; 158 159 emit DelegateeChanged(delegator, oldDelegatee, msg.sender); 160 return true; 161 } 162 163 /** 164 * Deposit / Withdraw 165 */ 166 /// @notice Deposit Ether to delegatee 167 function deposit(address delegatee) 168 external 169 payable 170 onlyInitialized 171 returns (bool) 172 { 173 require(msg.value >= MIN_DEPOSIT); 174 175 uint totalDeposit = _total_deposit[delegatee]; 176 uint deposit = _deposit[msg.sender][delegatee]; 177 uint stamina = _stamina[delegatee]; 178 179 // check overflow 180 require(totalDeposit + msg.value > totalDeposit); 181 require(deposit + msg.value > deposit); 182 require(stamina + msg.value > stamina); 183 184 _total_deposit[delegatee] = totalDeposit + msg.value; 185 _deposit[msg.sender][delegatee] = deposit + msg.value; 186 _stamina[delegatee] = stamina + msg.value; 187 188 if (_last_recovery_block[delegatee] == 0) { 189 _last_recovery_block[delegatee] = block.number; 190 } 191 192 emit Deposited(msg.sender, delegatee, msg.value); 193 return true; 194 } 195 196 /// @notice Request to withdraw deposit of delegatee. Ether can be withdrawn 197 /// after WITHDRAWAL_DELAY blocks 198 function requestWithdrawal(address delegatee, uint amount) 199 external 200 onlyInitialized 201 returns (bool) 202 { 203 require(amount > 0); 204 205 uint totalDeposit = _total_deposit[delegatee]; 206 uint deposit = _deposit[msg.sender][delegatee]; 207 uint stamina = _stamina[delegatee]; 208 209 require(deposit > 0); 210 211 // check underflow 212 require(totalDeposit - amount < totalDeposit); 213 require(deposit - amount < deposit); // this guarentees deposit >= amount 214 215 _total_deposit[delegatee] = totalDeposit - amount; 216 _deposit[msg.sender][delegatee] = deposit - amount; 217 218 // NOTE: Is it right to accept the request when stamina < amount? 219 if (stamina > amount) { 220 _stamina[delegatee] = stamina - amount; 221 } else { 222 _stamina[delegatee] = 0; 223 } 224 225 Withdrawal[] storage withdrawals = _withdrawal[msg.sender]; 226 227 uint withdrawalIndex = withdrawals.length; 228 Withdrawal storage withdrawal = withdrawals[withdrawals.length++]; 229 230 withdrawal.amount = uint128(amount); 231 withdrawal.requestBlockNumber = uint128(block.number); 232 withdrawal.delegatee = delegatee; 233 234 emit WithdrawalRequested(msg.sender, delegatee, amount, block.number, withdrawalIndex); 235 return true; 236 } 237 238 /// @notice Process last unprocessed withdrawal request. 239 function withdraw() external returns (bool) { 240 Withdrawal[] storage withdrawals = _withdrawal[msg.sender]; 241 require(withdrawals.length > 0); 242 243 uint lastWithdrawalIndex = _last_processed_withdrawal[msg.sender]; 244 uint withdrawalIndex; 245 246 if (lastWithdrawalIndex == 0 && !withdrawals[0].processed) { 247 withdrawalIndex = 0; 248 } else if (lastWithdrawalIndex == 0) { // lastWithdrawalIndex == 0 && withdrawals[0].processed 249 require(withdrawals.length >= 2); 250 251 withdrawalIndex = 1; 252 } else { 253 withdrawalIndex = lastWithdrawalIndex + 1; 254 } 255 256 // check out of index 257 require(withdrawalIndex < withdrawals.length); 258 259 Withdrawal storage withdrawal = _withdrawal[msg.sender][withdrawalIndex]; 260 261 // check withdrawal condition 262 require(!withdrawal.processed); 263 require(withdrawal.requestBlockNumber + WITHDRAWAL_DELAY <= block.number); 264 265 uint amount = uint(withdrawal.amount); 266 267 // update state 268 withdrawal.processed = true; 269 _last_processed_withdrawal[msg.sender] = withdrawalIndex; 270 271 // tranfser ether to depositor 272 msg.sender.transfer(amount); 273 emit Withdrawn(msg.sender, withdrawal.delegatee, amount, withdrawalIndex); 274 275 return true; 276 } 277 278 /** 279 * Stamina modification (only blockchain) 280 */ 281 /// @notice Add stamina of delegatee. The upper bound of stamina is total deposit of delegatee. 282 /// addStamina is called when remaining gas is refunded. So we can recover stamina 283 /// if RECOVER_EPOCH_LENGTH blocks are passed. 284 function addStamina(address delegatee, uint amount) external onlyChain returns (bool) { 285 // if enough blocks has passed since the last recovery, recover whole used stamina. 286 if (_last_recovery_block[delegatee] + RECOVER_EPOCH_LENGTH <= block.number) { 287 _stamina[delegatee] = _total_deposit[delegatee]; 288 _last_recovery_block[delegatee] = block.number; 289 _num_recovery[delegatee] += 1; 290 291 emit StaminaAdded(delegatee, 0, true); 292 return true; 293 } 294 295 uint totalDeposit = _total_deposit[delegatee]; 296 uint stamina = _stamina[delegatee]; 297 298 require(stamina + amount > stamina); 299 uint targetBalance = stamina + amount; 300 301 if (targetBalance > totalDeposit) _stamina[delegatee] = totalDeposit; 302 else _stamina[delegatee] = targetBalance; 303 304 emit StaminaAdded(delegatee, amount, false); 305 return true; 306 } 307 308 /// @notice Subtract stamina of delegatee. 309 function subtractStamina(address delegatee, uint amount) external onlyChain returns (bool) { 310 uint stamina = _stamina[delegatee]; 311 312 require(stamina - amount < stamina); 313 _stamina[delegatee] = stamina - amount; 314 315 emit StaminaSubtracted(delegatee, amount); 316 return true; 317 } 318 }