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  }