github.com/klaytn/klaytn@v1.12.1/contracts/cnstaking/CnStakingContract.sol (about) 1 // Copyright 2019 The klaytn Authors 2 // This file is part of the klaytn library. 3 // 4 // The klaytn library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The klaytn library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the klaytn library. If not, see <http://www.gnu.org/licenses/>. 16 17 pragma solidity 0.4.24; 18 import "./SafeMath.sol"; 19 20 contract CnStakingContract { 21 using SafeMath for uint256; 22 /* 23 * Events 24 */ 25 event DeployContract(string contractType, address contractValidator, address nodeId, address rewardAddress, address[] cnAdminList, uint256 requirement, uint256[] unlockTime, uint256[] unlockAmount); 26 event ReviewInitialConditions(address indexed from); 27 event CompleteReviewInitialConditions(); 28 event DepositLockupStakingAndInit(address from, uint256 value); 29 30 event SubmitRequest(uint256 indexed id, address indexed from, Functions functionId, bytes32 firstArg, bytes32 secondArg, bytes32 thirdArg); 31 event ConfirmRequest(uint256 indexed id, address indexed from, Functions functionId, bytes32 firstArg, bytes32 secondArg, bytes32 thirdArg, address[] confirmers); 32 event RevokeConfirmation(uint256 indexed id, address indexed from, Functions functionId, bytes32 firstArg, bytes32 secondArg, bytes32 thirdArg, address[] confirmers); 33 event CancelRequest(uint256 indexed id, address indexed from, Functions functionId, bytes32 firstArg, bytes32 secondArg, bytes32 thirdArg); 34 event ExecuteRequestSuccess(uint256 indexed id, address indexed from, Functions functionId, bytes32 firstArg, bytes32 secondArg, bytes32 thirdArg); 35 event ExecuteRequestFailure(uint256 indexed id, address indexed from, Functions functionId, bytes32 firstArg, bytes32 secondArg, bytes32 thirdArg); 36 event ClearRequest(); 37 38 event AddAdmin (address indexed admin); 39 event DeleteAdmin(address indexed admin); 40 event UpdateRequirement(uint256 requirement); 41 event WithdrawLockupStaking(address indexed to, uint256 value); 42 event ApproveStakingWithdrawal(uint256 approvedWithdrawalId, address to, uint256 value, uint256 withdrawableFrom); 43 event CancelApprovedStakingWithdrawal(uint256 approvedWithdrawalId, address to, uint256 value); 44 event UpdateRewardAddress(address rewardAddress); 45 46 event StakeKlay(address from, uint256 value); 47 event WithdrawApprovedStaking(uint256 approvedWithdrawalId, address to, uint256 value); 48 49 //AddressBook event 50 event ReviseRewardAddress(address cnNodeId, address prevRewardAddress, address curRewardAddress); 51 52 53 /* 54 * Constants 55 */ 56 uint256 constant public MAX_ADMIN = 50; 57 string constant public CONTRACT_TYPE = "CnStakingContract"; 58 uint256 constant public VERSION = 1; 59 address constant public ADDRESS_BOOK_ADDRESS = 0x0000000000000000000000000000000000000400; 60 uint256 constant public ONE_WEEK = 1 weeks; 61 62 63 /* 64 * Enums 65 */ 66 enum RequestState { Unknown, NotConfirmed, Executed, ExecutionFailed, Canceled } 67 enum Functions { Unknown, AddAdmin, DeleteAdmin, UpdateRequirement, ClearRequest, WithdrawLockupStaking, ApproveStakingWithdrawal, CancelApprovedStakingWithdrawal, UpdateRewardAddress } 68 enum WithdrawalStakingState { Unknown, Transferred, Canceled} 69 70 /* 71 * Storage 72 */ 73 address[] private adminList; 74 uint256 public requirement; 75 mapping (address => bool) private isAdmin; 76 uint256 public lastClearedId; 77 78 uint256 public requestCount; 79 mapping(uint256 => Request) private requestMap; 80 struct Request { 81 Functions functionId; 82 bytes32 firstArg; 83 bytes32 secondArg; 84 bytes32 thirdArg; 85 address requestProposer; 86 address[] confirmers; 87 RequestState state; 88 } 89 90 address public contractValidator; 91 bool public isInitialized; 92 struct LockupConditions { 93 uint256[] unlockTime; 94 uint256[] unlockAmount; 95 bool allReviewed; 96 uint256 reviewedCount; 97 mapping(address => bool) reviewedAdmin; 98 } 99 LockupConditions public lockupConditions; 100 uint256 public initialLockupStaking; 101 uint256 public remainingLockupStaking; 102 address public nodeId; 103 address public rewardAddress; 104 105 106 uint256 public staking; 107 uint256 public withdrawalRequestCount; 108 mapping(uint256 => WithdrawalRequest) private withdrawalRequestMap; 109 struct WithdrawalRequest { 110 address to; 111 uint256 value; 112 uint256 withdrawableFrom; 113 WithdrawalStakingState state; 114 } 115 116 117 /* 118 * Modifiers 119 */ 120 modifier onlyMultisigTx() { 121 require(msg.sender == address(this), "Not a multisig-transaction."); 122 _; 123 } 124 125 modifier onlyAdmin(address _admin) { 126 require(isAdmin[_admin], "Address is not admin."); 127 _; 128 } 129 130 modifier adminDoesNotExist(address _admin) { 131 require(!isAdmin[_admin], "Admin already exists."); 132 _; 133 } 134 135 modifier notNull(address _address) { 136 require(_address != 0, "Address is null"); 137 _; 138 } 139 140 modifier notConfirmedRequest(uint256 _id) { 141 require(requestMap[_id].state == RequestState.NotConfirmed, "Must be at not-confirmed state."); 142 _; 143 } 144 145 modifier validRequirement(uint256 _adminCount, uint256 _requirement) { 146 require(_adminCount <= MAX_ADMIN 147 && _requirement <= _adminCount 148 && _requirement != 0 149 && _adminCount != 0, "Invalid requirement."); 150 _; 151 } 152 153 modifier beforeInit() { 154 require(isInitialized == false, "Contract has been initialized."); 155 _; 156 } 157 158 modifier afterInit() { 159 require(isInitialized == true, "Contract is not initialized."); 160 _; 161 } 162 163 164 /* 165 * Constructor 166 */ 167 168 constructor(address _contractValidator, address _nodeId, address _rewardAddress, address[] _cnAdminlist, uint256 _requirement, uint256[] _unlockTime, uint256[] _unlockAmount) public 169 validRequirement(_cnAdminlist.length, _requirement) 170 notNull(_nodeId) 171 notNull(_rewardAddress) { 172 173 require(_contractValidator != 0, "Validator is null."); 174 isAdmin[_contractValidator] = true; 175 for(uint256 i = 0; i < _cnAdminlist.length; i++) { 176 require(!isAdmin[_cnAdminlist[i]] && _cnAdminlist[i] != 0, "Address is null or not unique."); 177 isAdmin[_cnAdminlist[i]] = true; 178 } 179 180 181 require(_unlockTime.length != 0 && _unlockAmount.length != 0 && _unlockTime.length == _unlockAmount.length, "Invalid unlock time and amount."); 182 uint256 unlockTime = now; 183 184 for (i = 0; i < _unlockAmount.length; i++) { 185 require(unlockTime < _unlockTime[i], "Unlock time is not in ascending order."); 186 require(_unlockAmount[i] > 0, "Amount is not positive number."); 187 unlockTime = _unlockTime[i]; 188 } 189 190 contractValidator = _contractValidator; 191 nodeId = _nodeId; 192 rewardAddress = _rewardAddress; 193 adminList = _cnAdminlist; 194 requirement = _requirement; 195 lockupConditions.unlockTime = _unlockTime; 196 lockupConditions.unlockAmount = _unlockAmount; 197 198 isInitialized = false; 199 emit DeployContract(CONTRACT_TYPE, _contractValidator, _nodeId, _rewardAddress, _cnAdminlist, _requirement, _unlockTime, _unlockAmount); 200 } 201 202 203 204 function reviewInitialConditions() external 205 onlyAdmin(msg.sender) 206 beforeInit() { 207 require(lockupConditions.reviewedAdmin[msg.sender] == false, "Msg.sender already reviewed."); 208 209 lockupConditions.reviewedAdmin[msg.sender] = true; 210 lockupConditions.reviewedCount = lockupConditions.reviewedCount.add(1); 211 212 emit ReviewInitialConditions(msg.sender); 213 214 215 if(lockupConditions.reviewedCount == adminList.length + 1) { 216 lockupConditions.allReviewed = true; 217 emit CompleteReviewInitialConditions(); 218 } 219 } 220 221 222 function depositLockupStakingAndInit() external payable 223 beforeInit() { 224 uint256 requiredStakingAmount; 225 uint256 cnt = lockupConditions.unlockAmount.length; 226 for(uint256 i = 0; i < cnt; i++) { 227 requiredStakingAmount = requiredStakingAmount.add(lockupConditions.unlockAmount[i]); 228 } 229 require(lockupConditions.allReviewed == true, "Reviewing is not finished."); 230 require(msg.value == requiredStakingAmount, "Value does not match."); 231 232 isAdmin[contractValidator] = false; 233 delete contractValidator; 234 235 initialLockupStaking = requiredStakingAmount; 236 remainingLockupStaking = requiredStakingAmount; 237 isInitialized = true; 238 emit DepositLockupStakingAndInit(msg.sender, msg.value); 239 } 240 241 242 /* 243 * Submit multisig function request 244 */ 245 246 /// @notice submit a request to add new admin at consensus node (multi-sig operation) 247 /// @param _admin new admin address to be added 248 function submitAddAdmin(address _admin) external 249 afterInit() 250 adminDoesNotExist(_admin) 251 notNull(_admin) 252 onlyAdmin(msg.sender) 253 validRequirement(adminList.length.add(1), requirement) { 254 uint256 id = requestCount; 255 submitRequest(id, Functions.AddAdmin, bytes32(_admin), 0, 0); 256 confirmRequest(id, requestMap[id].functionId, requestMap[id].firstArg, requestMap[id].secondArg, requestMap[id].thirdArg); 257 } 258 259 /// @notice submit a request to delete an admin at consensus node (multi-sig operation) 260 /// @param _admin address of the admin to be deleted 261 function submitDeleteAdmin(address _admin) external 262 afterInit() 263 onlyAdmin(_admin) 264 notNull(_admin) 265 onlyAdmin(msg.sender) 266 validRequirement(adminList.length.sub(1), requirement) { 267 uint256 id = requestCount; 268 submitRequest(id, Functions.DeleteAdmin, bytes32(_admin), 0, 0); 269 confirmRequest(id, requestMap[id].functionId, requestMap[id].firstArg, requestMap[id].secondArg, requestMap[id].thirdArg); 270 } 271 272 /// @notice submit a request to update the confirmation threshold (multi-sig operation) 273 /// @param _requirement new confirmation threshold 274 function submitUpdateRequirement(uint256 _requirement) external 275 afterInit() 276 onlyAdmin(msg.sender) 277 validRequirement(adminList.length, _requirement) { 278 require(_requirement != requirement, "Invalid value"); 279 uint256 id = requestCount; 280 submitRequest(id, Functions.UpdateRequirement, bytes32(_requirement), 0, 0); 281 confirmRequest(id, requestMap[id].functionId, requestMap[id].firstArg, requestMap[id].secondArg, requestMap[id].thirdArg); 282 } 283 284 /// @notice submit a request to clear all unfinalized request (multi-sig operation) 285 function submitClearRequest() external 286 afterInit() 287 onlyAdmin(msg.sender) { 288 uint256 id = requestCount; 289 submitRequest(id, Functions.ClearRequest, 0, 0, 0); 290 confirmRequest(id, requestMap[id].functionId, requestMap[id].firstArg, requestMap[id].secondArg, requestMap[id].thirdArg); 291 } 292 293 function submitWithdrawLockupStaking(address _to, uint256 _value) external 294 afterInit() 295 notNull(_to) 296 onlyAdmin(msg.sender) { 297 uint256 withdrawableStakingAmount; 298 ( , , , ,withdrawableStakingAmount) = getLockupStakingInfo(); 299 require(_value > 0 && _value <= withdrawableStakingAmount, "Invalid value."); 300 301 uint256 id = requestCount; 302 submitRequest(id, Functions.WithdrawLockupStaking, bytes32(_to), bytes32(_value), 0); 303 confirmRequest(id, requestMap[id].functionId, requestMap[id].firstArg, requestMap[id].secondArg, requestMap[id].thirdArg); 304 } 305 306 /// @notice submit a request to withdraw staked KLAY (multi-sig operation). 307 /// 7 days after "approveStakingWithdrawal" has been requested, 308 /// admins can call this function for actual withdrawal for another 7-day period. 309 /// If the admin doesn't withraw KLAY for that 7-day period, it expires. 310 /// @param _to target address to receive KLAY 311 /// @param _value withdrawl amount of KLAY 312 function submitApproveStakingWithdrawal(address _to, uint256 _value) external 313 afterInit() 314 notNull(_to) 315 onlyAdmin(msg.sender) { 316 require(_value > 0 && _value <= staking, "Invalid value."); 317 uint256 id = requestCount; 318 submitRequest(id, Functions.ApproveStakingWithdrawal, bytes32(_to), bytes32(_value), 0); 319 confirmRequest(id, requestMap[id].functionId, requestMap[id].firstArg, requestMap[id].secondArg, requestMap[id].thirdArg); 320 } 321 322 /// @notice submit a request to cancel KLAY withdrawl request (multi-sig operation). 323 /// @param _approvedWithdrawalId the withdrawal ID to cancel. The ID is acquired at the event log of ApproveStakingWithdrawal 324 function submitCancelApprovedStakingWithdrawal(uint256 _approvedWithdrawalId) external 325 afterInit() 326 onlyAdmin(msg.sender) { 327 require(withdrawalRequestMap[_approvedWithdrawalId].to != 0, "Withdrawal request does not exist."); 328 require(withdrawalRequestMap[_approvedWithdrawalId].state == WithdrawalStakingState.Unknown, "Invalid state."); 329 330 uint256 id = requestCount; 331 submitRequest(id, Functions.CancelApprovedStakingWithdrawal, bytes32(_approvedWithdrawalId), 0, 0); 332 confirmRequest(id, requestMap[id].functionId, requestMap[id].firstArg, requestMap[id].secondArg, requestMap[id].thirdArg); 333 } 334 335 /// @notice submit a request to update the reward address of consensus node (multi-sig operation). 336 /// @param _rewardAddress new reward address 337 function submitUpdateRewardAddress(address _rewardAddress) external 338 afterInit() 339 notNull(_rewardAddress) 340 onlyAdmin(msg.sender) { 341 uint256 id = requestCount; 342 submitRequest(id, Functions.UpdateRewardAddress, bytes32(_rewardAddress), 0, 0); 343 confirmRequest(id, requestMap[id].functionId, requestMap[id].firstArg, requestMap[id].secondArg, requestMap[id].thirdArg); 344 } 345 346 347 /* 348 * Confirm and revoke confirmation 349 */ 350 /// @notice after requesting of proposer, other admin confirm the request by confirmRequest 351 /// @param _id the request ID. It can be obtained by submitRequest event 352 /// @param _functionId the function ID of the request. It can be obtained by submitRequest event or getRequestInfo getter function 353 /// @param _firstArg the first argument of the request. It can be obtained by submitRequest event or getRequestInfo getter function 354 /// @param _secondArg the second argument of the request. If there is no second argument, then it should be 0. It can be obtained by submitRequest event or getRequestInfo getter function 355 /// @param _thirdArg the third argument of the request. If there is no second argument, then it should be 0. It can be obtained by submitRequest event or getRequestInfo getter function 356 function confirmRequest(uint256 _id, Functions _functionId, bytes32 _firstArg, bytes32 _secondArg, bytes32 _thirdArg) public 357 notConfirmedRequest(_id) 358 onlyAdmin(msg.sender) { 359 bool hasConfirmed = false; 360 uint256 confirmersCnt = requestMap[_id].confirmers.length; 361 for (uint256 i = 0; i < confirmersCnt; i++) { 362 if(msg.sender == requestMap[_id].confirmers[i]) { 363 hasConfirmed = true; 364 break; 365 } 366 } 367 require(!hasConfirmed, "Msg.sender already confirmed."); 368 require( 369 requestMap[_id].functionId == _functionId && 370 requestMap[_id].firstArg == _firstArg && 371 requestMap[_id].secondArg == _secondArg && 372 requestMap[_id].thirdArg == _thirdArg, "Function id and arguments do not match."); 373 374 requestMap[_id].confirmers.push(msg.sender); 375 376 address[] memory confirmers = requestMap[_id].confirmers; 377 emit ConfirmRequest(_id, msg.sender, _functionId, _firstArg, _secondArg, _thirdArg, confirmers); 378 379 380 if (checkQuorum(_id)) { 381 executeRequest(_id); 382 } 383 } 384 385 /// @notice revoking confirmation of each admin. If the admin is proposer, then the request will be cancled regardless of confirmation of other admins. 386 /// @param _id the request ID. It can be obtained by submitRequest event 387 /// @param _functionId the function ID of the request. It can be obtained by submitRequest event or getRequestInfo getter function 388 /// @param _firstArg the first argument of the request. It can be obtained by submitRequest event or getRequestInfo getter function 389 /// @param _secondArg the second argument of the request. If there is no second argument, then it should be 0. It can be obtained by submitRequest event or getRequestInfo getter function 390 /// @param _thirdArg the third argument of the request. If there is no second argument, then it should be 0. It can be obtained by submitRequest event or getRequestInfo getter function 391 function revokeConfirmation(uint256 _id, Functions _functionId, bytes32 _firstArg, bytes32 _secondArg, bytes32 _thirdArg) external 392 notConfirmedRequest(_id) 393 onlyAdmin(msg.sender) { 394 bool hasConfirmed = false; 395 uint256 confirmersCnt = requestMap[_id].confirmers.length; 396 for (uint256 i = 0; i < confirmersCnt; i++) { 397 if(msg.sender == requestMap[_id].confirmers[i]) { 398 hasConfirmed = true; 399 break; 400 } 401 } 402 require(hasConfirmed, "Msg.sender has not confirmed."); 403 require( 404 requestMap[_id].functionId == _functionId && 405 requestMap[_id].firstArg == _firstArg && 406 requestMap[_id].secondArg == _secondArg && 407 requestMap[_id].thirdArg == _thirdArg, "Function id and arguments do not match."); 408 409 revokeHandler(_id); 410 } 411 412 // this function separated from revokeConfirmation function because of 'stack too deep' issue of solidity 413 function revokeHandler(uint256 _id) private { 414 415 if (requestMap[_id].requestProposer == msg.sender) { 416 requestMap[_id].state = RequestState.Canceled; 417 emit CancelRequest(_id, msg.sender, requestMap[_id].functionId, requestMap[_id].firstArg, requestMap[_id].secondArg, requestMap[_id].thirdArg); 418 } else { 419 deleteFromConfirmerList(_id, msg.sender); 420 emit RevokeConfirmation(_id, msg.sender, requestMap[_id].functionId, requestMap[_id].firstArg, requestMap[_id].secondArg, requestMap[_id].thirdArg, requestMap[_id].confirmers); 421 } 422 } 423 424 425 /* 426 * Multisig functions 427 */ 428 function addAdmin(address _admin) external 429 onlyMultisigTx() 430 adminDoesNotExist(_admin) 431 validRequirement(adminList.length.add(1), requirement) { 432 isAdmin[_admin] = true; 433 adminList.push(_admin); 434 clearRequest(); 435 emit AddAdmin(_admin); 436 } 437 438 function deleteAdmin(address _admin) external 439 onlyMultisigTx() 440 onlyAdmin(_admin) 441 validRequirement(adminList.length.sub(1), requirement) { 442 isAdmin[_admin] = false; 443 444 uint256 adminCnt = adminList.length; 445 for (uint256 i = 0; i < adminCnt - 1; i++) { 446 if (adminList[i] == _admin) { 447 adminList[i] = adminList[adminCnt - 1]; 448 break; 449 } 450 } 451 delete adminList[adminCnt - 1]; 452 adminList.length = adminList.length.sub(1); 453 clearRequest(); 454 emit DeleteAdmin(_admin); 455 } 456 457 function updateRequirement(uint256 _requirement) external 458 onlyMultisigTx() 459 validRequirement(adminList.length, _requirement) { 460 requirement = _requirement; 461 clearRequest(); 462 emit UpdateRequirement(_requirement); 463 } 464 465 function clearRequest() public 466 onlyMultisigTx() { 467 for (uint256 i = lastClearedId; i < requestCount; i++){ 468 if (requestMap[i].state == RequestState.NotConfirmed) { 469 requestMap[i].state = RequestState.Canceled; 470 } 471 } 472 lastClearedId = requestCount; 473 emit ClearRequest(); 474 } 475 476 function withdrawLockupStaking(address _to, uint256 _value) external 477 onlyMultisigTx() { 478 uint256 withdrawableStakingAmount; 479 ( , , , ,withdrawableStakingAmount) = getLockupStakingInfo(); 480 require(withdrawableStakingAmount >= _value, "Value is not withdrawable."); 481 remainingLockupStaking = remainingLockupStaking.sub(_value); 482 483 _to.transfer(_value); 484 emit WithdrawLockupStaking(_to, _value); 485 } 486 487 488 function approveStakingWithdrawal(address _to, uint256 _value) external 489 onlyMultisigTx() { 490 require(_value <= staking, "Value is not withdrawable."); 491 uint256 approvedWithdrawalId = withdrawalRequestCount; 492 withdrawalRequestMap[approvedWithdrawalId] = WithdrawalRequest({ 493 to : _to, 494 value : _value, 495 withdrawableFrom : now + ONE_WEEK, 496 state: WithdrawalStakingState.Unknown 497 }); 498 withdrawalRequestCount = withdrawalRequestCount.add(1); 499 emit ApproveStakingWithdrawal(approvedWithdrawalId, _to, _value, now + ONE_WEEK); 500 } 501 502 503 function cancelApprovedStakingWithdrawal(uint256 _approvedWithdrawalId) external 504 onlyMultisigTx() { 505 require(withdrawalRequestMap[_approvedWithdrawalId].to != 0, "Withdrawal request does not exist."); 506 require(withdrawalRequestMap[_approvedWithdrawalId].state == WithdrawalStakingState.Unknown, "Invalid state."); 507 508 withdrawalRequestMap[_approvedWithdrawalId].state = WithdrawalStakingState.Canceled; 509 emit CancelApprovedStakingWithdrawal(_approvedWithdrawalId, withdrawalRequestMap[_approvedWithdrawalId].to, withdrawalRequestMap[_approvedWithdrawalId].value); 510 } 511 512 513 function updateRewardAddress(address _rewardAddress) external 514 onlyMultisigTx() { 515 rewardAddress = _rewardAddress; 516 AddressBookInterface(ADDRESS_BOOK_ADDRESS).reviseRewardAddress(_rewardAddress); 517 emit UpdateRewardAddress(rewardAddress); 518 } 519 520 521 /* 522 * Public functions 523 */ 524 /// @notice stake KLAY 525 function stakeKlay() external payable 526 afterInit() { 527 require(msg.value > 0, "Invalid amount."); 528 staking = staking.add(msg.value); 529 emit StakeKlay(msg.sender, msg.value); 530 } 531 532 /// @notice stake KLAY fallback function 533 function () external payable 534 afterInit() { 535 require(msg.value > 0, "Invalid amount."); 536 staking = staking.add(msg.value); 537 emit StakeKlay(msg.sender, msg.value); 538 } 539 540 /// @notice 7 days after "approveStakingWithdrawal" has been requested, admins can call this function for actual withdrawal. However, it's only available for 7 days 541 /// @param _approvedWithdrawalId the withdrawal ID to excute. The ID is acquired at the event log of ApproveStakingWithdrawal 542 function withdrawApprovedStaking(uint256 _approvedWithdrawalId) external 543 onlyAdmin(msg.sender) { 544 require(withdrawalRequestMap[_approvedWithdrawalId].to != 0, "Withdrawal request does not exist."); 545 require(withdrawalRequestMap[_approvedWithdrawalId].state == WithdrawalStakingState.Unknown, "Invalid state."); 546 require(withdrawalRequestMap[_approvedWithdrawalId].value <= staking, "Value is not withdrawable."); 547 require(now >= withdrawalRequestMap[_approvedWithdrawalId].withdrawableFrom, "Not withdrawable yet."); 548 if (now >= withdrawalRequestMap[_approvedWithdrawalId].withdrawableFrom + ONE_WEEK) { 549 550 withdrawalRequestMap[_approvedWithdrawalId].state = WithdrawalStakingState.Canceled; 551 emit CancelApprovedStakingWithdrawal(_approvedWithdrawalId, withdrawalRequestMap[_approvedWithdrawalId].to, withdrawalRequestMap[_approvedWithdrawalId].value); 552 } else { 553 staking = staking.sub(withdrawalRequestMap[_approvedWithdrawalId].value); 554 withdrawalRequestMap[_approvedWithdrawalId].state = WithdrawalStakingState.Transferred; 555 withdrawalRequestMap[_approvedWithdrawalId].to.transfer(withdrawalRequestMap[_approvedWithdrawalId].value); 556 emit WithdrawApprovedStaking(_approvedWithdrawalId, withdrawalRequestMap[_approvedWithdrawalId].to, withdrawalRequestMap[_approvedWithdrawalId].value); 557 } 558 } 559 560 561 /* 562 * Private functions 563 */ 564 /// @dev 565 function deleteFromConfirmerList(uint256 _id, address _admin) private { 566 uint256 confirmersCnt = requestMap[_id].confirmers.length; 567 for(uint256 i = 0; i < confirmersCnt; i++){ 568 if(_admin == requestMap[_id].confirmers[i]){ 569 570 571 if(i != confirmersCnt - 1) { 572 requestMap[_id].confirmers[i] = requestMap[_id].confirmers[confirmersCnt - 1]; 573 } 574 575 delete requestMap[_id].confirmers[confirmersCnt - 1]; 576 requestMap[_id].confirmers.length = confirmersCnt.sub(1); 577 break; 578 } 579 } 580 } 581 582 function submitRequest(uint256 _id, Functions _functionId, bytes32 _firstArg, bytes32 _secondArg, bytes32 _thirdArg) private { 583 requestMap[_id] = Request({ 584 functionId : _functionId, 585 firstArg : _firstArg, 586 secondArg : _secondArg, 587 thirdArg : _thirdArg, 588 requestProposer : msg.sender, 589 confirmers : new address[](0), 590 state: RequestState.NotConfirmed 591 }); 592 emit SubmitRequest(_id, msg.sender, _functionId, _firstArg, _secondArg, _thirdArg); 593 594 requestCount = requestCount.add(1); 595 } 596 597 function executeRequest(uint256 _id) private { 598 bool executed = false; 599 if (requestMap[_id].functionId == Functions.AddAdmin) { 600 //bytes4(keccak256("addAdmin(address)")) => 0x70480275 601 executed = address(this).call(0x70480275, address(requestMap[_id].firstArg)); 602 } 603 604 if (requestMap[_id].functionId == Functions.DeleteAdmin) { 605 //bytes4(keccak256("deleteAdmin(address)")) => 0x27e1f7df 606 executed = address(this).call(0x27e1f7df, address(requestMap[_id].firstArg)); 607 } 608 609 if (requestMap[_id].functionId == Functions.UpdateRequirement) { 610 //bytes4(keccak256("updateRequirement(uint256)")) => 0xc47afb3a 611 executed = address(this).call(0xc47afb3a, uint256(requestMap[_id].firstArg)); 612 } 613 614 if (requestMap[_id].functionId == Functions.ClearRequest) { 615 //bytes4(keccak256("clearRequest()")) => 0x4f97638f 616 executed = address(this).call(0x4f97638f); 617 } 618 619 if (requestMap[_id].functionId == Functions.WithdrawLockupStaking) { 620 //bytes4(keccak256("withdrawLockupStaking(address,uint256)")) => 0x505ebed4 621 executed = address(this).call(0x505ebed4, address(requestMap[_id].firstArg), uint256(requestMap[_id].secondArg)); 622 } 623 624 if (requestMap[_id].functionId == Functions.ApproveStakingWithdrawal) { 625 //bytes4(keccak256("approveStakingWithdrawal(address,uint256)")) => 0x5df8b09a 626 executed = address(this).call(0x5df8b09a, address(requestMap[_id].firstArg), uint256(requestMap[_id].secondArg)); 627 } 628 629 if (requestMap[_id].functionId == Functions.CancelApprovedStakingWithdrawal) { 630 //bytes4(keccak256("cancelApprovedStakingWithdrawal(uint256)")) => 0xc804b115 631 executed = address(this).call(0xc804b115, uint256(requestMap[_id].firstArg)); 632 } 633 634 if (requestMap[_id].functionId == Functions.UpdateRewardAddress) { 635 //bytes4(keccak256("updateRewardAddress(address)")) => 0x944dd5a2 636 executed = address(this).call(0x944dd5a2, address(requestMap[_id].firstArg)); 637 } 638 639 if(executed) { 640 requestMap[_id].state = RequestState.Executed; 641 emit ExecuteRequestSuccess(_id, msg.sender, requestMap[_id].functionId, requestMap[_id].firstArg, requestMap[_id].secondArg, requestMap[_id].thirdArg); 642 } else { 643 requestMap[_id].state = RequestState.ExecutionFailed; 644 emit ExecuteRequestFailure(_id, msg.sender, requestMap[_id].functionId, requestMap[_id].firstArg, requestMap[_id].secondArg, requestMap[_id].thirdArg); 645 } 646 } 647 648 function checkQuorum(uint256 _id) private view returns(bool) { 649 650 return (requestMap[_id].confirmers.length >= requirement); 651 } 652 653 654 /* 655 * Getter functions 656 */ 657 /// @dev 658 function getReviewers() external view 659 beforeInit() 660 returns(address[]) { 661 address[] memory reviewers = new address[](lockupConditions.reviewedCount); 662 uint256 id = 0; 663 if(lockupConditions.reviewedAdmin[contractValidator] == true) { 664 reviewers[id] = contractValidator; 665 id ++; 666 } 667 for(uint256 i = 0; i < adminList.length; i ++) { 668 if(lockupConditions.reviewedAdmin[adminList[i]] == true) { 669 reviewers[id] = adminList[i]; 670 id ++; 671 } 672 } 673 return reviewers; 674 } 675 676 /// @notice Queries for request id that matches entered state 677 /// @param _from beginning index 678 /// @param _to last index (if 0 or greater than total request count, it loops the whole list) 679 /// @param _state request state 680 /// @return uint256[] request IDs satisfying the conditions 681 function getRequestIds(uint256 _from, uint256 _to, RequestState _state) external view returns(uint256[]) { 682 uint256 lastIndex = _to; 683 if (_to == 0 || _to >= requestCount) { 684 lastIndex = requestCount; 685 } 686 require(lastIndex >= _from); 687 688 uint256 cnt = 0; 689 uint256 i; 690 691 for (i = _from; i < lastIndex; i++) { 692 if (requestMap[i].state == _state) { 693 cnt += 1; 694 } 695 } 696 uint256[] memory requestIds = new uint256[](cnt); 697 cnt = 0; 698 for (i = _from; i < lastIndex; i++) { 699 if (requestMap[i].state == _state) { 700 requestIds[cnt] = i; 701 cnt += 1; 702 } 703 } 704 return requestIds; 705 } 706 707 /// @notice get details of a request 708 /// @param _id request ID 709 /// @return function ID, first argument, second argument, third argument, request proposer, confirmers of the request, state of the request 710 function getRequestInfo(uint256 _id) external view returns( 711 Functions, 712 bytes32, 713 bytes32, 714 bytes32, 715 address, 716 address[], 717 RequestState) { 718 return( 719 requestMap[_id].functionId, 720 requestMap[_id].firstArg, 721 requestMap[_id].secondArg, 722 requestMap[_id].thirdArg, 723 requestMap[_id].requestProposer, 724 requestMap[_id].confirmers, 725 requestMap[_id].state 726 ); 727 } 728 729 function getLockupStakingInfo() public view 730 afterInit() 731 returns(uint256[], uint256[], uint256, uint256, uint256) { 732 uint256 currentTime = now; 733 uint256 unlockedAmount = 0; 734 735 uint256 cnt = lockupConditions.unlockTime.length; 736 for (uint256 i = 0; i < cnt; i++){ 737 if(currentTime > lockupConditions.unlockTime[i]) { 738 unlockedAmount = unlockedAmount.add(lockupConditions.unlockAmount[i]); 739 } 740 } 741 uint256 amountWithdrawn = initialLockupStaking.sub(remainingLockupStaking); 742 uint256 withdrawableLockupStaking = unlockedAmount.sub(amountWithdrawn); 743 744 return (lockupConditions.unlockTime, lockupConditions.unlockAmount, initialLockupStaking, remainingLockupStaking, withdrawableLockupStaking); 745 } 746 747 /// @notice loops withdrawalRequestMap and returns aprroved withdrawal ids 748 /// @param _from beginning index 749 /// @param _to last index (if 0 or greater than total request count, it loops the whole list) 750 /// @param _state withdrawal staking state 751 /// @return withdrawal IDs satisfying the conditions 752 function getApprovedStakingWithdrawalIds(uint256 _from, uint256 _to, WithdrawalStakingState _state) external view returns(uint256[]) { 753 uint256 lastIndex = _to; 754 if (_to == 0 || _to >= withdrawalRequestCount) { 755 lastIndex = withdrawalRequestCount; 756 } 757 require(lastIndex >= _from, "Invalid index."); 758 759 uint256 cnt = 0; 760 uint256 i; 761 762 for (i = _from; i < lastIndex; i++) { 763 if (withdrawalRequestMap[i].state == _state) { 764 cnt += 1; 765 } 766 } 767 uint256[] memory approvedWithdrawalIds = new uint256[](cnt); 768 cnt = 0; 769 for (i = _from; i < lastIndex; i++) { 770 if (withdrawalRequestMap[i].state == _state) { 771 approvedWithdrawalIds[cnt] = i; 772 cnt += 1; 773 } 774 } 775 return approvedWithdrawalIds; 776 } 777 778 /// @notice get details of approved staking withdrawal 779 /// @param _index staking withdrawal ID 780 /// @return withdrawal target address, wthdrawal KLAY value, time when it becomes available, withdrawal request state 781 function getApprovedStakingWithdrawalInfo(uint256 _index) external view returns(address, uint256, uint256, WithdrawalStakingState) { 782 return ( 783 withdrawalRequestMap[_index].to, 784 withdrawalRequestMap[_index].value, 785 withdrawalRequestMap[_index].withdrawableFrom, 786 withdrawalRequestMap[_index].state 787 ); 788 } 789 790 function getState() external view 791 returns(address, 792 address, 793 address, 794 address[], 795 uint256, 796 uint256[], 797 uint256[], 798 bool, 799 bool) { 800 return ( 801 contractValidator, 802 nodeId, 803 rewardAddress, 804 adminList, 805 requirement, 806 lockupConditions.unlockTime, 807 lockupConditions.unlockAmount, 808 lockupConditions.allReviewed, 809 isInitialized 810 ); 811 } 812 } 813 814 interface AddressBookInterface { 815 function reviseRewardAddress(address) external; 816 }