contract Staking is Redistributor {
    // The token to deposit
    address public property;

    /*** Events ***/
    // Event emitted when new property tokens is deposited
    event Deposit(address account, uint amount);

    // Event emitted when new property tokens is withdrawed
    event Withdraw(address account, uint amount);

    constructor(address property_, Distributor superior_) Redistributor(superior_) public {
        property = property_;
    }

    function totalDeposits() external view returns (uint) {
        return totalShares;
    }

    function accountState(address account) external view returns (uint, uint, uint) {
        AccountState memory state = accountStates[account];
        return (state.share, state.accruedIndex, state.accruedAmount);
    }

    // Deposit property tokens
    function deposit(uint amount) external returns (uint) {
        address account = msg.sender;

        // accrue & distribute
        accrueInternal();
        distributeInternal(account);

        // transfer property token in
        uint actualAmount = doTransferIn(account, amount);

        // update storage
        AccountState storage state = accountStates[account];
        totalShares = add_(totalShares, actualAmount);
        state.share = add_(state.share, actualAmount);

        emit Deposit(account, actualAmount);

        return actualAmount;
    }

    // Withdraw property tokens
    function withdraw(uint amount) external returns (uint) {
        address account = msg.sender;
        AccountState storage state = accountStates[account];
        require(state.share >= amount, "withdraw: insufficient value");

        // accrue & distribute
        accrueInternal();
        distributeInternal(account);

        // decrease total deposits
        totalShares = sub_(totalShares, amount);
        state.share = sub_(state.share, amount);

        // transfer property tokens back to account
        doTransferOut(account, amount);

        emit Withdraw(account, amount);

        return amount;
    }

    /*** Safe Token ***/

    /**
     * @dev Similar to EIP20 transfer, except it handles a False result from `transferFrom` and reverts in that case.
     *      This will revert due to insufficient balance or insufficient allowance.
     *      This function returns the actual amount received,
     *      which may be less than `amount` if there is a fee attached to the transfer.
     *
     *      Note: This wrapper safely handles non-standard ERC-20 tokens that do not return a value.
     *            See here: https://medium.com/coinmonks/missing-return-value-bug-at-least-130-tokens-affected-d67bf08521ca
     */
    function doTransferIn(address from, uint amount) internal returns (uint) {
        EIP20NonStandardInterface token = EIP20NonStandardInterface(property);
        uint balanceBefore = EIP20Interface(property).balanceOf(address(this));
        token.transferFrom(from, address(this), amount);

        bool success;
        assembly {
            switch returndatasize()
                case 0 {                       // This is a non-standard ERC-20
                    success := not(0)          // set success to true
                }
                case 32 {                      // This is a compliant ERC-20
                    returndatacopy(0, 0, 32)
                    success := mload(0)        // Set `success = returndata` of external call
                }
                default {                      // This is an excessively non-compliant ERC-20, revert.
                    revert(0, 0)
                }
        }
        require(success, "TOKEN_TRANSFER_IN_FAILED");

        // Calculate the amount that was *actually* transferred
        uint balanceAfter = EIP20Interface(property).balanceOf(address(this));
        require(balanceAfter >= balanceBefore, "TOKEN_TRANSFER_IN_OVERFLOW");
        return balanceAfter - balanceBefore;   // underflow already checked above, just subtract
    }

    /**
     * @dev Similar to EIP20 transfer, except it handles a False success from `transfer` and returns an explanatory
     *      error code rather than reverting. If caller has not called checked protocol's balance, this may revert due to
     *      insufficient cash held in this contract. If caller has checked protocol's balance prior to this call, and verified
     *      it is >= amount, this should not revert in normal conditions.
     *
     *      Note: This wrapper safely handles non-standard ERC-20 tokens that do not return a value.
     *            See here: https://medium.com/coinmonks/missing-return-value-bug-at-least-130-tokens-affected-d67bf08521ca
     */
    function doTransferOut(address to, uint amount) internal {
        EIP20NonStandardInterface token = EIP20NonStandardInterface(property);
        token.transfer(to, amount);

        bool success;
        assembly {
            switch returndatasize()
                case 0 {                      // This is a non-standard ERC-20
                    success := not(0)          // set success to true
                }
                case 32 {                     // This is a complaint ERC-20
                    returndatacopy(0, 0, 32)
                    success := mload(0)        // Set `success = returndata` of external call
                }
                default {                     // This is an excessively non-compliant ERC-20, revert.
                    revert(0, 0)
                }
        }
        require(success, "TOKEN_TRANSFER_OUT_FAILED");
    }
}
function deposit(uint amount) external returns (uint) {
    address account = msg.sender;

    // accrue & distribute
    accrueInternal();
    distributeInternal(account);

    // transfer property token in
    uint actualAmount = doTransferIn(account, amount);

    // update storage
    AccountState storage state = accountStates[account];
    totalShares = add_(totalShares, actualAmount);
    state.share = add_(state.share, actualAmount);

    emit Deposit(account, actualAmount);

    return actualAmount;
}

// Deposit property tokens