ETH Price: $3,178.78 (+3.00%)

Contract

0x612B4367a7Ae2cf346dC3759623a9c22102ff8d6
 

Overview

ETH Balance

0 ETH

Eth Value

$0.00

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To
0x60806040111033822020-10-22 2:46:501488 days ago1603334810IN
 Create: DelegateManager
0 ETH0.3075994480

View more zero value Internal Transactions in Advanced View mode

Advanced mode:
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
DelegateManager

Compiler Version
v0.5.17+commit.d19bba13

Optimization Enabled:
Yes with 200 runs

Other Settings:
istanbul EvmVersion, Apache-2.0 license

Contract Source Code (Solidity)

/**
 *Submitted for verification at Etherscan.io on 2020-10-22
*/

// File: @openzeppelin/upgrades/contracts/Initializable.sol

pragma solidity >=0.4.24 <0.7.0;


/**
 * @title Initializable
 *
 * @dev Helper contract to support initializer functions. To use it, replace
 * the constructor with a function that has the `initializer` modifier.
 * WARNING: Unlike constructors, initializer functions must be manually
 * invoked. This applies both to deploying an Initializable contract, as well
 * as extending an Initializable contract via inheritance.
 * WARNING: When used with inheritance, manual care must be taken to not invoke
 * a parent initializer twice, or ensure that all initializers are idempotent,
 * because this is not dealt with automatically as with constructors.
 */
contract Initializable {

  /**
   * @dev Indicates that the contract has been initialized.
   */
  bool private initialized;

  /**
   * @dev Indicates that the contract is in the process of being initialized.
   */
  bool private initializing;

  /**
   * @dev Modifier to use in the initializer function of a contract.
   */
  modifier initializer() {
    require(initializing || isConstructor() || !initialized, "Contract instance has already been initialized");

    bool isTopLevelCall = !initializing;
    if (isTopLevelCall) {
      initializing = true;
      initialized = true;
    }

    _;

    if (isTopLevelCall) {
      initializing = false;
    }
  }

  /// @dev Returns true if and only if the function is running in the constructor
  function isConstructor() private view returns (bool) {
    // extcodesize checks the size of the code stored in an address, and
    // address returns the current address. Since the code is still not
    // deployed when running a constructor, any checks on its code size will
    // yield zero, making it an effective way to detect if a contract is
    // under construction or not.
    address self = address(this);
    uint256 cs;
    assembly { cs := extcodesize(self) }
    return cs == 0;
  }

  // Reserved storage space to allow for layout changes in the future.
  uint256[50] private ______gap;
}

// File: @openzeppelin/contracts-ethereum-package/contracts/GSN/Context.sol

pragma solidity ^0.5.0;


/*
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with GSN meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
contract Context is Initializable {
    // Empty internal constructor, to prevent people from mistakenly deploying
    // an instance of this contract, which should be used via inheritance.
    constructor () internal { }
    // solhint-disable-previous-line no-empty-blocks

    function _msgSender() internal view returns (address payable) {
        return msg.sender;
    }

    function _msgData() internal view returns (bytes memory) {
        this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
        return msg.data;
    }
}

// File: @openzeppelin/contracts-ethereum-package/contracts/token/ERC20/IERC20.sol

pragma solidity ^0.5.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP. Does not include
 * the optional functions; to access them see {ERC20Detailed}.
 */
interface IERC20 {
    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the amount of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves `amount` tokens from the caller's account to `recipient`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address recipient, uint256 amount) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `sender` to `recipient` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);

    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);
}

// File: @openzeppelin/contracts-ethereum-package/contracts/math/SafeMath.sol

pragma solidity ^0.5.0;

/**
 * @dev Wrappers over Solidity's arithmetic operations with added overflow
 * checks.
 *
 * Arithmetic operations in Solidity wrap on overflow. This can easily result
 * in bugs, because programmers usually assume that an overflow raises an
 * error, which is the standard behavior in high level programming languages.
 * `SafeMath` restores this intuition by reverting the transaction when an
 * operation overflows.
 *
 * Using this library instead of the unchecked operations eliminates an entire
 * class of bugs, so it's recommended to use it always.
 */
library SafeMath {
    /**
     * @dev Returns the addition of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `+` operator.
     *
     * Requirements:
     * - Addition cannot overflow.
     */
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a, "SafeMath: addition overflow");

        return c;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting on
     * overflow (when the result is negative).
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     * - Subtraction cannot overflow.
     */
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        return sub(a, b, "SafeMath: subtraction overflow");
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
     * overflow (when the result is negative).
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     * - Subtraction cannot overflow.
     *
     * _Available since v2.4.0._
     */
    function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b <= a, errorMessage);
        uint256 c = a - b;

        return c;
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `*` operator.
     *
     * Requirements:
     * - Multiplication cannot overflow.
     */
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
        // benefit is lost if 'b' is also tested.
        // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
        if (a == 0) {
            return 0;
        }

        uint256 c = a * b;
        require(c / a == b, "SafeMath: multiplication overflow");

        return c;
    }

    /**
     * @dev Returns the integer division of two unsigned integers. Reverts on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     * - The divisor cannot be zero.
     */
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        return div(a, b, "SafeMath: division by zero");
    }

    /**
     * @dev Returns the integer division of two unsigned integers. Reverts with custom message on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     * - The divisor cannot be zero.
     *
     * _Available since v2.4.0._
     */
    function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        // Solidity only automatically asserts when dividing by 0
        require(b > 0, errorMessage);
        uint256 c = a / b;
        // assert(a == b * c + a % b); // There is no case in which this doesn't hold

        return c;
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * Reverts when dividing by zero.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     * - The divisor cannot be zero.
     */
    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        return mod(a, b, "SafeMath: modulo by zero");
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * Reverts with custom message when dividing by zero.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     * - The divisor cannot be zero.
     *
     * _Available since v2.4.0._
     */
    function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b != 0, errorMessage);
        return a % b;
    }
}

// File: @openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20.sol

pragma solidity ^0.5.0;





/**
 * @dev Implementation of the {IERC20} interface.
 *
 * This implementation is agnostic to the way tokens are created. This means
 * that a supply mechanism has to be added in a derived contract using {_mint}.
 * For a generic mechanism see {ERC20Mintable}.
 *
 * TIP: For a detailed writeup see our guide
 * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How
 * to implement supply mechanisms].
 *
 * We have followed general OpenZeppelin guidelines: functions revert instead
 * of returning `false` on failure. This behavior is nonetheless conventional
 * and does not conflict with the expectations of ERC20 applications.
 *
 * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
 * This allows applications to reconstruct the allowance for all accounts just
 * by listening to said events. Other implementations of the EIP may not emit
 * these events, as it isn't required by the specification.
 *
 * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
 * functions have been added to mitigate the well-known issues around setting
 * allowances. See {IERC20-approve}.
 */
contract ERC20 is Initializable, Context, IERC20 {
    using SafeMath for uint256;

    mapping (address => uint256) private _balances;

    mapping (address => mapping (address => uint256)) private _allowances;

    uint256 private _totalSupply;

    /**
     * @dev See {IERC20-totalSupply}.
     */
    function totalSupply() public view returns (uint256) {
        return _totalSupply;
    }

    /**
     * @dev See {IERC20-balanceOf}.
     */
    function balanceOf(address account) public view returns (uint256) {
        return _balances[account];
    }

    /**
     * @dev See {IERC20-transfer}.
     *
     * Requirements:
     *
     * - `recipient` cannot be the zero address.
     * - the caller must have a balance of at least `amount`.
     */
    function transfer(address recipient, uint256 amount) public returns (bool) {
        _transfer(_msgSender(), recipient, amount);
        return true;
    }

    /**
     * @dev See {IERC20-allowance}.
     */
    function allowance(address owner, address spender) public view returns (uint256) {
        return _allowances[owner][spender];
    }

    /**
     * @dev See {IERC20-approve}.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function approve(address spender, uint256 amount) public returns (bool) {
        _approve(_msgSender(), spender, amount);
        return true;
    }

    /**
     * @dev See {IERC20-transferFrom}.
     *
     * Emits an {Approval} event indicating the updated allowance. This is not
     * required by the EIP. See the note at the beginning of {ERC20};
     *
     * Requirements:
     * - `sender` and `recipient` cannot be the zero address.
     * - `sender` must have a balance of at least `amount`.
     * - the caller must have allowance for `sender`'s tokens of at least
     * `amount`.
     */
    function transferFrom(address sender, address recipient, uint256 amount) public returns (bool) {
        _transfer(sender, recipient, amount);
        _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance"));
        return true;
    }

    /**
     * @dev Atomically increases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function increaseAllowance(address spender, uint256 addedValue) public returns (bool) {
        _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue));
        return true;
    }

    /**
     * @dev Atomically decreases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `spender` must have allowance for the caller of at least
     * `subtractedValue`.
     */
    function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) {
        _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero"));
        return true;
    }

    /**
     * @dev Moves tokens `amount` from `sender` to `recipient`.
     *
     * This is internal function is equivalent to {transfer}, and can be used to
     * e.g. implement automatic token fees, slashing mechanisms, etc.
     *
     * Emits a {Transfer} event.
     *
     * Requirements:
     *
     * - `sender` cannot be the zero address.
     * - `recipient` cannot be the zero address.
     * - `sender` must have a balance of at least `amount`.
     */
    function _transfer(address sender, address recipient, uint256 amount) internal {
        require(sender != address(0), "ERC20: transfer from the zero address");
        require(recipient != address(0), "ERC20: transfer to the zero address");

        _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
        _balances[recipient] = _balances[recipient].add(amount);
        emit Transfer(sender, recipient, amount);
    }

    /** @dev Creates `amount` tokens and assigns them to `account`, increasing
     * the total supply.
     *
     * Emits a {Transfer} event with `from` set to the zero address.
     *
     * Requirements
     *
     * - `to` cannot be the zero address.
     */
    function _mint(address account, uint256 amount) internal {
        require(account != address(0), "ERC20: mint to the zero address");

        _totalSupply = _totalSupply.add(amount);
        _balances[account] = _balances[account].add(amount);
        emit Transfer(address(0), account, amount);
    }

    /**
     * @dev Destroys `amount` tokens from `account`, reducing the
     * total supply.
     *
     * Emits a {Transfer} event with `to` set to the zero address.
     *
     * Requirements
     *
     * - `account` cannot be the zero address.
     * - `account` must have at least `amount` tokens.
     */
    function _burn(address account, uint256 amount) internal {
        require(account != address(0), "ERC20: burn from the zero address");

        _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance");
        _totalSupply = _totalSupply.sub(amount);
        emit Transfer(account, address(0), amount);
    }

    /**
     * @dev Sets `amount` as the allowance of `spender` over the `owner`s tokens.
     *
     * This is internal function is equivalent to `approve`, and can be used to
     * e.g. set automatic allowances for certain subsystems, etc.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `owner` cannot be the zero address.
     * - `spender` cannot be the zero address.
     */
    function _approve(address owner, address spender, uint256 amount) internal {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");

        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }

    /**
     * @dev Destroys `amount` tokens from `account`.`amount` is then deducted
     * from the caller's allowance.
     *
     * See {_burn} and {_approve}.
     */
    function _burnFrom(address account, uint256 amount) internal {
        _burn(account, amount);
        _approve(account, _msgSender(), _allowances[account][_msgSender()].sub(amount, "ERC20: burn amount exceeds allowance"));
    }

    uint256[50] private ______gap;
}

// File: @openzeppelin/contracts-ethereum-package/contracts/access/Roles.sol

pragma solidity ^0.5.0;

/**
 * @title Roles
 * @dev Library for managing addresses assigned to a Role.
 */
library Roles {
    struct Role {
        mapping (address => bool) bearer;
    }

    /**
     * @dev Give an account access to this role.
     */
    function add(Role storage role, address account) internal {
        require(!has(role, account), "Roles: account already has role");
        role.bearer[account] = true;
    }

    /**
     * @dev Remove an account's access to this role.
     */
    function remove(Role storage role, address account) internal {
        require(has(role, account), "Roles: account does not have role");
        role.bearer[account] = false;
    }

    /**
     * @dev Check if an account has this role.
     * @return bool
     */
    function has(Role storage role, address account) internal view returns (bool) {
        require(account != address(0), "Roles: account is the zero address");
        return role.bearer[account];
    }
}

// File: @openzeppelin/contracts-ethereum-package/contracts/access/roles/MinterRole.sol

pragma solidity ^0.5.0;




contract MinterRole is Initializable, Context {
    using Roles for Roles.Role;

    event MinterAdded(address indexed account);
    event MinterRemoved(address indexed account);

    Roles.Role private _minters;

    function initialize(address sender) public initializer {
        if (!isMinter(sender)) {
            _addMinter(sender);
        }
    }

    modifier onlyMinter() {
        require(isMinter(_msgSender()), "MinterRole: caller does not have the Minter role");
        _;
    }

    function isMinter(address account) public view returns (bool) {
        return _minters.has(account);
    }

    function addMinter(address account) public onlyMinter {
        _addMinter(account);
    }

    function renounceMinter() public {
        _removeMinter(_msgSender());
    }

    function _addMinter(address account) internal {
        _minters.add(account);
        emit MinterAdded(account);
    }

    function _removeMinter(address account) internal {
        _minters.remove(account);
        emit MinterRemoved(account);
    }

    uint256[50] private ______gap;
}

// File: @openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20Mintable.sol

pragma solidity ^0.5.0;




/**
 * @dev Extension of {ERC20} that adds a set of accounts with the {MinterRole},
 * which have permission to mint (create) new tokens as they see fit.
 *
 * At construction, the deployer of the contract is the only minter.
 */
contract ERC20Mintable is Initializable, ERC20, MinterRole {
    function initialize(address sender) public initializer {
        MinterRole.initialize(sender);
    }

    /**
     * @dev See {ERC20-_mint}.
     *
     * Requirements:
     *
     * - the caller must have the {MinterRole}.
     */
    function mint(address account, uint256 amount) public onlyMinter returns (bool) {
        _mint(account, amount);
        return true;
    }

    uint256[50] private ______gap;
}

// File: @openzeppelin/contracts-ethereum-package/contracts/utils/Address.sol

pragma solidity ^0.5.5;

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev Returns true if `account` is a contract.
     *
     * [IMPORTANT]
     * ====
     * It is unsafe to assume that an address for which this function returns
     * false is an externally-owned account (EOA) and not a contract.
     *
     * Among others, `isContract` will return false for the following 
     * types of addresses:
     *
     *  - an externally-owned account
     *  - a contract in construction
     *  - an address where a contract will be created
     *  - an address where a contract lived, but was destroyed
     * ====
     */
    function isContract(address account) internal view returns (bool) {
        // According to EIP-1052, 0x0 is the value returned for not-yet created accounts
        // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
        // for accounts without code, i.e. `keccak256('')`
        bytes32 codehash;
        bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
        // solhint-disable-next-line no-inline-assembly
        assembly { codehash := extcodehash(account) }
        return (codehash != accountHash && codehash != 0x0);
    }

    /**
     * @dev Converts an `address` into `address payable`. Note that this is
     * simply a type cast: the actual underlying value is not changed.
     *
     * _Available since v2.4.0._
     */
    function toPayable(address account) internal pure returns (address payable) {
        return address(uint160(account));
    }

    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     *
     * _Available since v2.4.0._
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        require(address(this).balance >= amount, "Address: insufficient balance");

        // solhint-disable-next-line avoid-call-value
        (bool success, ) = recipient.call.value(amount)("");
        require(success, "Address: unable to send value, recipient may have reverted");
    }
}

// File: @openzeppelin/contracts-ethereum-package/contracts/token/ERC20/SafeERC20.sol

pragma solidity ^0.5.0;




/**
 * @title SafeERC20
 * @dev Wrappers around ERC20 operations that throw on failure (when the token
 * contract returns false). Tokens that return no value (and instead revert or
 * throw on failure) are also supported, non-reverting calls are assumed to be
 * successful.
 * To use this library you can add a `using SafeERC20 for ERC20;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeERC20 {
    using SafeMath for uint256;
    using Address for address;

    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
    }

    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
        callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
    }

    function safeApprove(IERC20 token, address spender, uint256 value) internal {
        // safeApprove should only be called when setting an initial allowance,
        // or when resetting it to zero. To increase and decrease it, use
        // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
        // solhint-disable-next-line max-line-length
        require((value == 0) || (token.allowance(address(this), spender) == 0),
            "SafeERC20: approve from non-zero to non-zero allowance"
        );
        callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
    }

    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 newAllowance = token.allowance(address(this), spender).add(value);
        callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
    }

    function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero");
        callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     */
    function callOptionalReturn(IERC20 token, bytes memory data) private {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves.

        // A Solidity high level call has three parts:
        //  1. The target address is checked to verify it contains contract code
        //  2. The call itself is made, and success asserted
        //  3. The return value is decoded, which in turn checks the size of the returned data.
        // solhint-disable-next-line max-line-length
        require(address(token).isContract(), "SafeERC20: call to non-contract");

        // solhint-disable-next-line avoid-low-level-calls
        (bool success, bytes memory returndata) = address(token).call(data);
        require(success, "SafeERC20: low-level call failed");

        if (returndata.length > 0) { // Return data is optional
            // solhint-disable-next-line max-line-length
            require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
        }
    }
}

// File: @openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20Burnable.sol

pragma solidity ^0.5.0;




/**
 * @dev Extension of {ERC20} that allows token holders to destroy both their own
 * tokens and those that they have an allowance for, in a way that can be
 * recognized off-chain (via event analysis).
 */
contract ERC20Burnable is Initializable, Context, ERC20 {
    /**
     * @dev Destroys `amount` tokens from the caller.
     *
     * See {ERC20-_burn}.
     */
    function burn(uint256 amount) public {
        _burn(_msgSender(), amount);
    }

    /**
     * @dev See {ERC20-_burnFrom}.
     */
    function burnFrom(address account, uint256 amount) public {
        _burnFrom(account, amount);
    }

    uint256[50] private ______gap;
}

// File: @aragon/court/contracts/lib/Checkpointing.sol

pragma solidity ^0.5.8;


/**
* @title Checkpointing - Library to handle a historic set of numeric values
*/
library Checkpointing {
    uint256 private constant MAX_UINT192 = uint256(uint192(-1));

    string private constant ERROR_VALUE_TOO_BIG = "CHECKPOINT_VALUE_TOO_BIG";
    string private constant ERROR_CANNOT_ADD_PAST_VALUE = "CHECKPOINT_CANNOT_ADD_PAST_VALUE";

    /**
    * @dev To specify a value at a given point in time, we need to store two values:
    *      - `time`: unit-time value to denote the first time when a value was registered
    *      - `value`: a positive numeric value to registered at a given point in time
    *
    *      Note that `time` does not need to refer necessarily to a timestamp value, any time unit could be used
    *      for it like block numbers, terms, etc.
    */
    struct Checkpoint {
        uint64 time;
        uint192 value;
    }

    /**
    * @dev A history simply denotes a list of checkpoints
    */
    struct History {
        Checkpoint[] history;
    }

    /**
    * @dev Add a new value to a history for a given point in time. This function does not allow to add values previous
    *      to the latest registered value, if the value willing to add corresponds to the latest registered value, it
    *      will be updated.
    * @param self Checkpoints history to be altered
    * @param _time Point in time to register the given value
    * @param _value Numeric value to be registered at the given point in time
    */
    function add(History storage self, uint64 _time, uint256 _value) internal {
        require(_value <= MAX_UINT192, ERROR_VALUE_TOO_BIG);
        _add192(self, _time, uint192(_value));
    }

    /**
    * @dev Fetch the latest registered value of history, it will return zero if there was no value registered
    * @param self Checkpoints history to be queried
    */
    function getLast(History storage self) internal view returns (uint256) {
        uint256 length = self.history.length;
        if (length > 0) {
            return uint256(self.history[length - 1].value);
        }

        return 0;
    }

    /**
    * @dev Fetch the most recent registered past value of a history based on a given point in time that is not known
    *      how recent it is beforehand. It will return zero if there is no registered value or if given time is
    *      previous to the first registered value.
    *      It uses a binary search.
    * @param self Checkpoints history to be queried
    * @param _time Point in time to query the most recent registered past value of
    */
    function get(History storage self, uint64 _time) internal view returns (uint256) {
        return _binarySearch(self, _time);
    }

    /**
    * @dev Fetch the most recent registered past value of a history based on a given point in time. It will return zero
    *      if there is no registered value or if given time is previous to the first registered value.
    *      It uses a linear search starting from the end.
    * @param self Checkpoints history to be queried
    * @param _time Point in time to query the most recent registered past value of
    */
    function getRecent(History storage self, uint64 _time) internal view returns (uint256) {
        return _backwardsLinearSearch(self, _time);
    }

    /**
    * @dev Private function to add a new value to a history for a given point in time. This function does not allow to
    *      add values previous to the latest registered value, if the value willing to add corresponds to the latest
    *      registered value, it will be updated.
    * @param self Checkpoints history to be altered
    * @param _time Point in time to register the given value
    * @param _value Numeric value to be registered at the given point in time
    */
    function _add192(History storage self, uint64 _time, uint192 _value) private {
        uint256 length = self.history.length;
        if (length == 0 || self.history[self.history.length - 1].time < _time) {
            // If there was no value registered or the given point in time is after the latest registered value,
            // we can insert it to the history directly.
            self.history.push(Checkpoint(_time, _value));
        } else {
            // If the point in time given for the new value is not after the latest registered value, we must ensure
            // we are only trying to update the latest value, otherwise we would be changing past data.
            Checkpoint storage currentCheckpoint = self.history[length - 1];
            require(_time == currentCheckpoint.time, ERROR_CANNOT_ADD_PAST_VALUE);
            currentCheckpoint.value = _value;
        }
    }

    /**
    * @dev Private function to execute a backwards linear search to find the most recent registered past value of a
    *      history based on a given point in time. It will return zero if there is no registered value or if given time
    *      is previous to the first registered value. Note that this function will be more suitable when we already know
    *      that the time used to index the search is recent in the given history.
    * @param self Checkpoints history to be queried
    * @param _time Point in time to query the most recent registered past value of
    */
    function _backwardsLinearSearch(History storage self, uint64 _time) private view returns (uint256) {
        // If there was no value registered for the given history return simply zero
        uint256 length = self.history.length;
        if (length == 0) {
            return 0;
        }

        uint256 index = length - 1;
        Checkpoint storage checkpoint = self.history[index];
        while (index > 0 && checkpoint.time > _time) {
            index--;
            checkpoint = self.history[index];
        }

        return checkpoint.time > _time ? 0 : uint256(checkpoint.value);
    }

    /**
    * @dev Private function execute a binary search to find the most recent registered past value of a history based on
    *      a given point in time. It will return zero if there is no registered value or if given time is previous to
    *      the first registered value. Note that this function will be more suitable when don't know how recent the
    *      time used to index may be.
    * @param self Checkpoints history to be queried
    * @param _time Point in time to query the most recent registered past value of
    */
    function _binarySearch(History storage self, uint64 _time) private view returns (uint256) {
        // If there was no value registered for the given history return simply zero
        uint256 length = self.history.length;
        if (length == 0) {
            return 0;
        }

        // If the requested time is equal to or after the time of the latest registered value, return latest value
        uint256 lastIndex = length - 1;
        if (_time >= self.history[lastIndex].time) {
            return uint256(self.history[lastIndex].value);
        }

        // If the requested time is previous to the first registered value, return zero to denote missing checkpoint
        if (_time < self.history[0].time) {
            return 0;
        }

        // Execute a binary search between the checkpointed times of the history
        uint256 low = 0;
        uint256 high = lastIndex;

        while (high > low) {
            // No need for SafeMath: for this to overflow array size should be ~2^255
            uint256 mid = (high + low + 1) / 2;
            Checkpoint storage checkpoint = self.history[mid];
            uint64 midTime = checkpoint.time;

            if (_time > midTime) {
                low = mid;
            } else if (_time < midTime) {
                // No need for SafeMath: high > low >= 0 => high >= 1 => mid >= 1
                high = mid - 1;
            } else {
                return uint256(checkpoint.value);
            }
        }

        return uint256(self.history[low].value);
    }
}

// File: @aragon/court/contracts/lib/os/Uint256Helpers.sol

// Brought from https://github.com/aragon/aragonOS/blob/v4.3.0/contracts/common/Uint256Helpers.sol
// Adapted to use pragma ^0.5.8 and satisfy our linter rules

pragma solidity ^0.5.8;


library Uint256Helpers {
    uint256 private constant MAX_UINT8 = uint8(-1);
    uint256 private constant MAX_UINT64 = uint64(-1);

    string private constant ERROR_UINT8_NUMBER_TOO_BIG = "UINT8_NUMBER_TOO_BIG";
    string private constant ERROR_UINT64_NUMBER_TOO_BIG = "UINT64_NUMBER_TOO_BIG";

    function toUint8(uint256 a) internal pure returns (uint8) {
        require(a <= MAX_UINT8, ERROR_UINT8_NUMBER_TOO_BIG);
        return uint8(a);
    }

    function toUint64(uint256 a) internal pure returns (uint64) {
        require(a <= MAX_UINT64, ERROR_UINT64_NUMBER_TOO_BIG);
        return uint64(a);
    }
}

// File: contracts/InitializableV2.sol

pragma solidity >=0.4.24 <0.7.0;



/**
 * Wrapper around OpenZeppelin's Initializable contract.
 * Exposes initialized state management to ensure logic contract functions cannot be called before initialization.
 * This is needed because OZ's Initializable contract no longer exposes initialized state variable.
 * https://github.com/OpenZeppelin/openzeppelin-sdk/blob/v2.8.0/packages/lib/contracts/Initializable.sol
 */
contract InitializableV2 is Initializable {
    bool private isInitialized;

    string private constant ERROR_NOT_INITIALIZED = "InitializableV2: Not initialized";

    /**
     * @notice wrapper function around parent contract Initializable's `initializable` modifier
     *      initializable modifier ensures this function can only be called once by each deployed child contract
     *      sets isInitialized flag to true to which is used by _requireIsInitialized()
     */
    function initialize() public initializer {
        isInitialized = true;
    }

    /**
     * @notice Reverts transaction if isInitialized is false. Used by child contracts to ensure
     *      contract is initialized before functions can be called.
     */
    function _requireIsInitialized() internal view {
        require(isInitialized == true, ERROR_NOT_INITIALIZED);
    }

    /**
     * @notice Exposes isInitialized bool var to child contracts with read-only access
     */
    function _isInitialized() internal view returns (bool) {
        return isInitialized;
    }
}

// File: @openzeppelin/contracts-ethereum-package/contracts/ownership/Ownable.sol

pragma solidity ^0.5.0;



/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be aplied to your functions to restrict their use to
 * the owner.
 */
contract Ownable is Initializable, Context {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    function initialize(address sender) public initializer {
        _owner = sender;
        emit OwnershipTransferred(address(0), _owner);
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        require(isOwner(), "Ownable: caller is not the owner");
        _;
    }

    /**
     * @dev Returns true if the caller is the current owner.
     */
    function isOwner() public view returns (bool) {
        return _msgSender() == _owner;
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions anymore. Can only be called by the current owner.
     *
     * > Note: Renouncing ownership will leave the contract without an owner,
     * thereby removing any functionality that is only available to the owner.
     */
    function renounceOwnership() public onlyOwner {
        emit OwnershipTransferred(_owner, address(0));
        _owner = address(0);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public onlyOwner {
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     */
    function _transferOwnership(address newOwner) internal {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        emit OwnershipTransferred(_owner, newOwner);
        _owner = newOwner;
    }

    uint256[50] private ______gap;
}

// File: contracts/registry/Registry.sol

pragma solidity ^0.5.0;





/**
* @title Central hub for Audius protocol. It stores all contract addresses to facilitate
*   external access and enable version management.
*/
contract Registry is InitializableV2, Ownable {
    using SafeMath for uint256;

    /**
     * @dev addressStorage mapping allows efficient lookup of current contract version
     *      addressStorageHistory maintains record of all contract versions
     */
    mapping(bytes32 => address) private addressStorage;
    mapping(bytes32 => address[]) private addressStorageHistory;

    event ContractAdded(
        bytes32 indexed _name,
        address indexed _address
    );

    event ContractRemoved(
        bytes32 indexed _name,
        address indexed _address
    );

    event ContractUpgraded(
        bytes32 indexed _name,
        address indexed _oldAddress,
        address indexed _newAddress
    );

    function initialize() public initializer {
        /// @notice Ownable.initialize(address _sender) sets contract owner to _sender.
        Ownable.initialize(msg.sender);
        InitializableV2.initialize();
    }

    // ========================================= Setters =========================================

    /**
     * @notice addContract registers contract name to address mapping under given registry key
     * @param _name - registry key that will be used for lookups
     * @param _address - address of contract
     */
    function addContract(bytes32 _name, address _address) external onlyOwner {
        _requireIsInitialized();

        require(
            addressStorage[_name] == address(0x00),
            "Registry: Contract already registered with given name."
        );
        require(
            _address != address(0x00),
            "Registry: Cannot register zero address."
        );

        setAddress(_name, _address);

        emit ContractAdded(_name, _address);
    }

    /**
     * @notice removes contract address registered under given registry key
     * @param _name - registry key for lookup
     */
    function removeContract(bytes32 _name) external onlyOwner {
        _requireIsInitialized();

        address contractAddress = addressStorage[_name];
        require(
            contractAddress != address(0x00),
            "Registry: Cannot remove - no contract registered with given _name."
        );

        setAddress(_name, address(0x00));

        emit ContractRemoved(_name, contractAddress);
    }

    /**
     * @notice replaces contract address registered under given key with provided address
     * @param _name - registry key for lookup
     * @param _newAddress - new contract address to register under given key
     */
    function upgradeContract(bytes32 _name, address _newAddress) external onlyOwner {
        _requireIsInitialized();

        address oldAddress = addressStorage[_name];
        require(
            oldAddress != address(0x00),
            "Registry: Cannot upgrade - no contract registered with given _name."
        );
        require(
            _newAddress != address(0x00),
            "Registry: Cannot upgrade - cannot register zero address."
        );

        setAddress(_name, _newAddress);

        emit ContractUpgraded(_name, oldAddress, _newAddress);
    }

    // ========================================= Getters =========================================

    /**
     * @notice returns contract address registered under given registry key
     * @param _name - registry key for lookup
     * @return contractAddr - address of contract registered under given registry key
     */
    function getContract(bytes32 _name) external view returns (address contractAddr) {
        _requireIsInitialized();

        return addressStorage[_name];
    }

    /// @notice overloaded getContract to return explicit version of contract
    function getContract(bytes32 _name, uint256 _version) external view
    returns (address contractAddr)
    {
        _requireIsInitialized();

        // array length for key implies version number
        require(
            _version <= addressStorageHistory[_name].length,
            "Registry: Index out of range _version."
        );
        return addressStorageHistory[_name][_version.sub(1)];
    }

    /**
     * @notice Returns the number of versions for a contract key
     * @param _name - registry key for lookup
     * @return number of contract versions
     */
    function getContractVersionCount(bytes32 _name) external view returns (uint256) {
        _requireIsInitialized();

        return addressStorageHistory[_name].length;
    }

    // ========================================= Private functions =========================================

    /**
     * @param _key the key for the contract address
     * @param _value the contract address
     */
    function setAddress(bytes32 _key, address _value) private {
        // main map for cheap lookup
        addressStorage[_key] = _value;
        // keep track of contract address history
        addressStorageHistory[_key].push(_value);
    }

}

// File: contracts/Governance.sol

pragma solidity ^0.5.0;




contract Governance is InitializableV2 {
    using SafeMath for uint256;

    string private constant ERROR_ONLY_GOVERNANCE = (
        "Governance: Only callable by self"
    );
    string private constant ERROR_INVALID_VOTING_PERIOD = (
        "Governance: Requires non-zero _votingPeriod"
    );
    string private constant ERROR_INVALID_REGISTRY = (
        "Governance: Requires non-zero _registryAddress"
    );
    string private constant ERROR_INVALID_VOTING_QUORUM = (
        "Governance: Requires _votingQuorumPercent between 1 & 100"
    );

    /**
     * @notice Address and contract instance of Audius Registry. Used to ensure this contract
     *      can only govern contracts that are registered in the Audius Registry.
     */
    Registry private registry;

    /// @notice Address of Audius staking contract, used to permission Governance method calls
    address private stakingAddress;

    /// @notice Address of Audius ServiceProvider contract, used to permission Governance method calls
    address private serviceProviderFactoryAddress;

    /// @notice Address of Audius DelegateManager contract, used to permission Governance method calls
    address private delegateManagerAddress;

    /// @notice Period in blocks for which a governance proposal is open for voting
    uint256 private votingPeriod;

    /// @notice Number of blocks that must pass after votingPeriod has expired before proposal can be evaluated/executed
    uint256 private executionDelay;

    /// @notice Required minimum percentage of total stake to have voted to consider a proposal valid
    ///         Percentaged stored as a uint256 between 0 & 100
    ///         Calculated as: 100 * sum of voter stakes / total staked in Staking (at proposal submission block)
    uint256 private votingQuorumPercent;

    /// @notice Max number of InProgress proposals possible at once
    /// @dev uint16 gives max possible value of 65,535
    uint16 private maxInProgressProposals;

    /**
     * @notice Address of account that has special Governance permissions. Can veto proposals
     *      and execute transactions directly on contracts.
     */
    address private guardianAddress;

    /***** Enums *****/

    /**
     * @notice All Proposal Outcome states.
     *      InProgress - Proposal is active and can be voted on.
     *      Rejected - Proposal votingPeriod has closed and vote failed to pass. Proposal will not be executed.
     *      ApprovedExecuted - Proposal votingPeriod has closed and vote passed. Proposal was successfully executed.
     *      QuorumNotMet - Proposal votingPeriod has closed and votingQuorumPercent was not met. Proposal will not be executed.
     *      ApprovedExecutionFailed - Proposal vote passed, but transaction execution failed.
     *      Evaluating - Proposal vote passed, and evaluateProposalOutcome function is currently running.
     *          This status is transiently used inside that function to prevent re-entrancy.
     *      Vetoed - Proposal was vetoed by Guardian.
     *      TargetContractAddressChanged - Proposal considered invalid since target contract address changed
     *      TargetContractCodeHashChanged - Proposal considered invalid since code has at target contract address has changed
     */
    enum Outcome {
        InProgress,
        Rejected,
        ApprovedExecuted,
        QuorumNotMet,
        ApprovedExecutionFailed,
        Evaluating,
        Vetoed,
        TargetContractAddressChanged,
        TargetContractCodeHashChanged
    }

    /**
     * @notice All Proposal Vote states for a voter.
     *      None - The default state, for any account that has not previously voted on this Proposal.
     *      No - The account voted No on this Proposal.
     *      Yes - The account voted Yes on this Proposal.
     * @dev Enum values map to uints, so first value in Enum always is 0.
     */
    enum Vote {None, No, Yes}

    struct Proposal {
        uint256 proposalId;
        address proposer;
        uint256 submissionBlockNumber;
        bytes32 targetContractRegistryKey;
        address targetContractAddress;
        uint256 callValue;
        string functionSignature;
        bytes callData;
        Outcome outcome;
        uint256 voteMagnitudeYes;
        uint256 voteMagnitudeNo;
        uint256 numVotes;
        mapping(address => Vote) votes;
        mapping(address => uint256) voteMagnitudes;
        bytes32 contractHash;
    }

    /***** Proposal storage *****/

    /// @notice ID of most recently created proposal. Ids are monotonically increasing and 1-indexed.
    uint256 lastProposalId = 0;

    /// @notice mapping of proposalId to Proposal struct with all proposal state
    mapping(uint256 => Proposal) proposals;

    /// @notice array of proposals with InProgress state
    uint256[] inProgressProposals;


    /***** Events *****/
    event ProposalSubmitted(
        uint256 indexed _proposalId,
        address indexed _proposer,
        string _name,
        string _description
    );
    event ProposalVoteSubmitted(
        uint256 indexed _proposalId,
        address indexed _voter,
        Vote indexed _vote,
        uint256 _voterStake
    );
    event ProposalVoteUpdated(
        uint256 indexed _proposalId,
        address indexed _voter,
        Vote indexed _vote,
        uint256 _voterStake,
        Vote _previousVote
    );
    event ProposalOutcomeEvaluated(
        uint256 indexed _proposalId,
        Outcome indexed _outcome,
        uint256 _voteMagnitudeYes,
        uint256 _voteMagnitudeNo,
        uint256 _numVotes
    );
    event ProposalTransactionExecuted(
        uint256 indexed _proposalId,
        bool indexed _success,
        bytes _returnData
    );
    event GuardianTransactionExecuted(
        address indexed _targetContractAddress,
        uint256 _callValue,
        string indexed _functionSignature,
        bytes indexed _callData,
        bytes _returnData
    );
    event ProposalVetoed(uint256 indexed _proposalId);
    event RegistryAddressUpdated(address indexed _newRegistryAddress);
    event GuardianshipTransferred(address indexed _newGuardianAddress);
    event VotingPeriodUpdated(uint256 indexed _newVotingPeriod);
    event ExecutionDelayUpdated(uint256 indexed _newExecutionDelay);
    event VotingQuorumPercentUpdated(uint256 indexed _newVotingQuorumPercent);
    event MaxInProgressProposalsUpdated(uint256 indexed _newMaxInProgressProposals);

    /**
     * @notice Initialize the Governance contract
     * @dev _votingPeriod <= DelegateManager.undelegateLockupDuration
     * @dev stakingAddress must be initialized separately after Staking contract is deployed
     * @param _registryAddress - address of the registry proxy contract
     * @param _votingPeriod - period in blocks for which a governance proposal is open for voting
     * @param _executionDelay - number of blocks that must pass after votingPeriod has expired before proposal can be evaluated/executed
     * @param _votingQuorumPercent - required minimum percentage of total stake to have voted to consider a proposal valid
     * @param _maxInProgressProposals - max number of InProgress proposals possible at once
     * @param _guardianAddress - address of account that has special Governance permissions
     */
    function initialize(
        address _registryAddress,
        uint256 _votingPeriod,
        uint256 _executionDelay,
        uint256 _votingQuorumPercent,
        uint16 _maxInProgressProposals,
        address _guardianAddress
    ) public initializer {
        require(_registryAddress != address(0x00), ERROR_INVALID_REGISTRY);
        registry = Registry(_registryAddress);

        require(_votingPeriod > 0, ERROR_INVALID_VOTING_PERIOD);
        votingPeriod = _votingPeriod;

        // executionDelay does not have to be non-zero
        executionDelay = _executionDelay;

        require(
            _maxInProgressProposals > 0,
            "Governance: Requires non-zero _maxInProgressProposals"
        );
        maxInProgressProposals = _maxInProgressProposals;

        require(
            _votingQuorumPercent > 0 && _votingQuorumPercent <= 100,
            ERROR_INVALID_VOTING_QUORUM
        );
        votingQuorumPercent = _votingQuorumPercent;

        require(
            _guardianAddress != address(0x00),
            "Governance: Requires non-zero _guardianAddress"
        );
        guardianAddress = _guardianAddress;  //Guardian address becomes the only party

        InitializableV2.initialize();
    }

    // ========================================= Governance Actions =========================================

    /**
     * @notice Submit a proposal for vote. Only callable by addresses with non-zero total active stake.
     *      total active stake = total active deployer stake + total active delegator stake
     *
     * @dev _name and _description length is not enforced since they aren't stored on-chain and only event emitted
     *
     * @param _targetContractRegistryKey - Registry key for the contract concerning this proposal
     * @param _callValue - amount of wei to pass with function call if a token transfer is involved
     * @param _functionSignature - function signature of the function to be executed if proposal is successful
     * @param _callData - encoded value(s) to call function with if proposal is successful
     * @param _name - Text name of proposal to be emitted in event
     * @param _description - Text description of proposal to be emitted in event
     *
     * @return - ID of new proposal
     */
    function submitProposal(
        bytes32 _targetContractRegistryKey,
        uint256 _callValue,
        string calldata _functionSignature,
        bytes calldata _callData,
        string calldata _name,
        string calldata _description
    ) external returns (uint256)
    {
        _requireIsInitialized();
        _requireStakingAddressIsSet();
        _requireServiceProviderFactoryAddressIsSet();
        _requireDelegateManagerAddressIsSet();

        address proposer = msg.sender;

        // Require all InProgress proposals that can be evaluated have been evaluated before new proposal submission
        require(
            this.inProgressProposalsAreUpToDate(),
            "Governance: Cannot submit new proposal until all evaluatable InProgress proposals are evaluated."
        );

        // Require new proposal submission would not push number of InProgress proposals over max number
        require(
            inProgressProposals.length < maxInProgressProposals,
            "Governance: Number of InProgress proposals already at max. Please evaluate if possible, or wait for current proposals' votingPeriods to expire."
        );

        // Require proposer has non-zero total active stake or is guardian address
        require(
            _calculateAddressActiveStake(proposer) > 0 || proposer == guardianAddress,
            "Governance: Proposer must be address with non-zero total active stake or be guardianAddress."
        );

        // Require _targetContractRegistryKey points to a valid registered contract
        address targetContractAddress = registry.getContract(_targetContractRegistryKey);
        require(
            targetContractAddress != address(0x00),
            "Governance: _targetContractRegistryKey must point to valid registered contract"
        );

        // Signature cannot be empty
        require(
            bytes(_functionSignature).length != 0,
            "Governance: _functionSignature cannot be empty."
        );

        // Require non-zero description length
        require(bytes(_description).length > 0, "Governance: _description length must be > 0");

        // Require non-zero name length
        require(bytes(_name).length > 0, "Governance: _name length must be > 0");

        // set proposalId
        uint256 newProposalId = lastProposalId.add(1);

        // Store new Proposal obj in proposals mapping
        proposals[newProposalId] = Proposal({
            proposalId: newProposalId,
            proposer: proposer,
            submissionBlockNumber: block.number,
            targetContractRegistryKey: _targetContractRegistryKey,
            targetContractAddress: targetContractAddress,
            callValue: _callValue,
            functionSignature: _functionSignature,
            callData: _callData,
            outcome: Outcome.InProgress,
            voteMagnitudeYes: 0,
            voteMagnitudeNo: 0,
            numVotes: 0,
            contractHash: _getCodeHash(targetContractAddress)
            /* votes: mappings are auto-initialized to default state */
            /* voteMagnitudes: mappings are auto-initialized to default state */
        });

        // Append new proposalId to inProgressProposals array
        inProgressProposals.push(newProposalId);

        emit ProposalSubmitted(
            newProposalId,
            proposer,
            _name,
            _description
        );

        lastProposalId = newProposalId;

        return newProposalId;
    }

    /**
     * @notice Vote on an active Proposal. Only callable by addresses with non-zero active stake.
     * @param _proposalId - id of the proposal this vote is for
     * @param _vote - can be either {Yes, No} from Vote enum. No other values allowed
     */
    function submitVote(uint256 _proposalId, Vote _vote) external {
        _requireIsInitialized();
        _requireStakingAddressIsSet();
        _requireServiceProviderFactoryAddressIsSet();
        _requireDelegateManagerAddressIsSet();
        _requireValidProposalId(_proposalId);

        address voter = msg.sender;

        // Require proposal votingPeriod is still active
        uint256 submissionBlockNumber = proposals[_proposalId].submissionBlockNumber;
        uint256 endBlockNumber = submissionBlockNumber.add(votingPeriod);
        require(
            block.number > submissionBlockNumber && block.number <= endBlockNumber,
            "Governance: Proposal votingPeriod has ended"
        );

        // Require voter has non-zero total active stake
        uint256 voterActiveStake = _calculateAddressActiveStake(voter);
        require(
            voterActiveStake > 0,
            "Governance: Voter must be address with non-zero total active stake."
        );

        // Require previous vote is None
        require(
            proposals[_proposalId].votes[voter] == Vote.None,
            "Governance: To update previous vote, call updateVote()"
        );

        // Require vote is either Yes or No
        require(
            _vote == Vote.Yes || _vote == Vote.No,
            "Governance: Can only submit a Yes or No vote"
        );

        // Record vote
        proposals[_proposalId].votes[voter] = _vote;

        // Record voteMagnitude for voter
        proposals[_proposalId].voteMagnitudes[voter] = voterActiveStake;

        // Update proposal cumulative vote magnitudes
        if (_vote == Vote.Yes) {
            _increaseVoteMagnitudeYes(_proposalId, voterActiveStake);
        } else {
            _increaseVoteMagnitudeNo(_proposalId, voterActiveStake);
        }

        // Increment proposal numVotes
        proposals[_proposalId].numVotes = proposals[_proposalId].numVotes.add(1);

        emit ProposalVoteSubmitted(
            _proposalId,
            voter,
            _vote,
            voterActiveStake
        );
    }

    /**
     * @notice Update previous vote on an active Proposal. Only callable by addresses with non-zero active stake.
     * @param _proposalId - id of the proposal this vote is for
     * @param _vote - can be either {Yes, No} from Vote enum. No other values allowed
     */
    function updateVote(uint256 _proposalId, Vote _vote) external {
        _requireIsInitialized();
        _requireStakingAddressIsSet();
        _requireServiceProviderFactoryAddressIsSet();
        _requireDelegateManagerAddressIsSet();
        _requireValidProposalId(_proposalId);

        address voter = msg.sender;

        // Require proposal votingPeriod is still active
        uint256 submissionBlockNumber = proposals[_proposalId].submissionBlockNumber;
        uint256 endBlockNumber = submissionBlockNumber.add(votingPeriod);
        require(
            block.number > submissionBlockNumber && block.number <= endBlockNumber,
            "Governance: Proposal votingPeriod has ended"
        );

        // Retrieve previous vote
        Vote previousVote = proposals[_proposalId].votes[voter];

        // Require previous vote is not None
        require(
            previousVote != Vote.None,
            "Governance: To submit new vote, call submitVote()"
        );

        // Require vote is either Yes or No
        require(
            _vote == Vote.Yes || _vote == Vote.No,
            "Governance: Can only submit a Yes or No vote"
        );

        // Record updated vote
        proposals[_proposalId].votes[voter] = _vote;

        // Update vote magnitudes, using vote magnitude from when previous vote was submitted
        uint256 voteMagnitude = proposals[_proposalId].voteMagnitudes[voter];
        if (previousVote == Vote.Yes && _vote == Vote.No) {
            _decreaseVoteMagnitudeYes(_proposalId, voteMagnitude);
            _increaseVoteMagnitudeNo(_proposalId, voteMagnitude);
        } else if (previousVote == Vote.No && _vote == Vote.Yes) {
            _decreaseVoteMagnitudeNo(_proposalId, voteMagnitude);
            _increaseVoteMagnitudeYes(_proposalId, voteMagnitude);
        }
        // If _vote == previousVote, no changes needed to vote magnitudes.

        // Do not update numVotes

        emit ProposalVoteUpdated(
            _proposalId,
            voter,
            _vote,
            voteMagnitude,
            previousVote
        );
    }

    /**
     * @notice Once the voting period + executionDelay for a proposal has ended, evaluate the outcome and
     *      execute the proposal if voting quorum met & vote passes.
     *      To pass, stake-weighted vote must be > 50% Yes.
     * @dev Requires that caller is an active staker at the time the proposal is created
     * @param _proposalId - id of the proposal
     * @return Outcome of proposal evaluation
     */
    function evaluateProposalOutcome(uint256 _proposalId)
    external returns (Outcome)
    {
        _requireIsInitialized();
        _requireStakingAddressIsSet();
        _requireServiceProviderFactoryAddressIsSet();
        _requireDelegateManagerAddressIsSet();
        _requireValidProposalId(_proposalId);

        // Require proposal has not already been evaluated.
        require(
            proposals[_proposalId].outcome == Outcome.InProgress,
            "Governance: Can only evaluate InProgress proposal."
        );

        // Re-entrancy should not be possible here since this switches the status of the
        // proposal to 'Evaluating' so it should fail the status is 'InProgress' check
        proposals[_proposalId].outcome = Outcome.Evaluating;

        // Require proposal votingPeriod + executionDelay have ended.
        uint256 submissionBlockNumber = proposals[_proposalId].submissionBlockNumber;
        uint256 endBlockNumber = submissionBlockNumber.add(votingPeriod).add(executionDelay);
        require(
            block.number > endBlockNumber,
            "Governance: Proposal votingPeriod & executionDelay must end before evaluation."
        );

        address targetContractAddress = registry.getContract(
            proposals[_proposalId].targetContractRegistryKey
        );

        Outcome outcome;

        // target contract address changed -> close proposal without execution.
        if (targetContractAddress != proposals[_proposalId].targetContractAddress) {
            outcome = Outcome.TargetContractAddressChanged;
        }
        // target contract code hash changed -> close proposal without execution.
        else if (_getCodeHash(targetContractAddress) != proposals[_proposalId].contractHash) {
            outcome = Outcome.TargetContractCodeHashChanged;
        }
        // voting quorum not met -> close proposal without execution.
        else if (_quorumMet(proposals[_proposalId], Staking(stakingAddress)) == false) {
            outcome = Outcome.QuorumNotMet;
        }
        // votingQuorumPercent met & vote passed -> execute proposed transaction & close proposal.
        else if (
            proposals[_proposalId].voteMagnitudeYes > proposals[_proposalId].voteMagnitudeNo
        ) {
            (bool success, bytes memory returnData) = _executeTransaction(
                targetContractAddress,
                proposals[_proposalId].callValue,
                proposals[_proposalId].functionSignature,
                proposals[_proposalId].callData
            );

            emit ProposalTransactionExecuted(
                _proposalId,
                success,
                returnData
            );

            // Proposal outcome depends on success of transaction execution.
            if (success) {
                outcome = Outcome.ApprovedExecuted;
            } else {
                outcome = Outcome.ApprovedExecutionFailed;
            }
        }
        // votingQuorumPercent met & vote did not pass -> close proposal without transaction execution.
        else {
            outcome = Outcome.Rejected;
        }

        // This records the final outcome in the proposals mapping
        proposals[_proposalId].outcome = outcome;

        // Remove from inProgressProposals array
        _removeFromInProgressProposals(_proposalId);

        emit ProposalOutcomeEvaluated(
            _proposalId,
            outcome,
            proposals[_proposalId].voteMagnitudeYes,
            proposals[_proposalId].voteMagnitudeNo,
            proposals[_proposalId].numVotes
        );

        return outcome;
    }

    /**
     * @notice Action limited to the guardian address that can veto a proposal
     * @param _proposalId - id of the proposal
     */
    function vetoProposal(uint256 _proposalId) external {
        _requireIsInitialized();
        _requireValidProposalId(_proposalId);

        require(
            msg.sender == guardianAddress,
            "Governance: Only guardian can veto proposals."
        );

        require(
            proposals[_proposalId].outcome == Outcome.InProgress,
            "Governance: Cannot veto inactive proposal."
        );

        proposals[_proposalId].outcome = Outcome.Vetoed;

        // Remove from inProgressProposals array
        _removeFromInProgressProposals(_proposalId);

        emit ProposalVetoed(_proposalId);
    }

    // ========================================= Config Setters =========================================

    /**
     * @notice Set the Staking address
     * @dev Only callable by self via _executeTransaction
     * @param _stakingAddress - address for new Staking contract
     */
    function setStakingAddress(address _stakingAddress) external {
        _requireIsInitialized();

        require(msg.sender == address(this), ERROR_ONLY_GOVERNANCE);
        require(_stakingAddress != address(0x00), "Governance: Requires non-zero _stakingAddress");
        stakingAddress = _stakingAddress;
    }

    /**
     * @notice Set the ServiceProviderFactory address
     * @dev Only callable by self via _executeTransaction
     * @param _serviceProviderFactoryAddress - address for new ServiceProviderFactory contract
     */
    function setServiceProviderFactoryAddress(address _serviceProviderFactoryAddress) external {
        _requireIsInitialized();

        require(msg.sender == address(this), ERROR_ONLY_GOVERNANCE);
        require(
            _serviceProviderFactoryAddress != address(0x00),
            "Governance: Requires non-zero _serviceProviderFactoryAddress"
        );
        serviceProviderFactoryAddress = _serviceProviderFactoryAddress;
    }

    /**
     * @notice Set the DelegateManager address
     * @dev Only callable by self via _executeTransaction
     * @param _delegateManagerAddress - address for new DelegateManager contract
     */
    function setDelegateManagerAddress(address _delegateManagerAddress) external {
        _requireIsInitialized();

        require(msg.sender == address(this), ERROR_ONLY_GOVERNANCE);
        require(
            _delegateManagerAddress != address(0x00),
            "Governance: Requires non-zero _delegateManagerAddress"
        );
        delegateManagerAddress = _delegateManagerAddress;
    }

    /**
     * @notice Set the voting period for a Governance proposal
     * @dev Only callable by self via _executeTransaction
     * @param _votingPeriod - new voting period
     */
    function setVotingPeriod(uint256 _votingPeriod) external {
        _requireIsInitialized();

        require(msg.sender == address(this), ERROR_ONLY_GOVERNANCE);
        require(_votingPeriod > 0, ERROR_INVALID_VOTING_PERIOD);
        votingPeriod = _votingPeriod;
        emit VotingPeriodUpdated(_votingPeriod);
    }

    /**
     * @notice Set the voting quorum percentage for a Governance proposal
     * @dev Only callable by self via _executeTransaction
     * @param _votingQuorumPercent - new voting period
     */
    function setVotingQuorumPercent(uint256 _votingQuorumPercent) external {
        _requireIsInitialized();

        require(msg.sender == address(this), ERROR_ONLY_GOVERNANCE);
        require(
            _votingQuorumPercent > 0 && _votingQuorumPercent <= 100,
            ERROR_INVALID_VOTING_QUORUM
        );
        votingQuorumPercent = _votingQuorumPercent;
        emit VotingQuorumPercentUpdated(_votingQuorumPercent);
    }

    /**
     * @notice Set the Registry address
     * @dev Only callable by self via _executeTransaction
     * @param _registryAddress - address for new Registry contract
     */
    function setRegistryAddress(address _registryAddress) external {
        _requireIsInitialized();

        require(msg.sender == address(this), ERROR_ONLY_GOVERNANCE);
        require(_registryAddress != address(0x00), ERROR_INVALID_REGISTRY);

        registry = Registry(_registryAddress);

        emit RegistryAddressUpdated(_registryAddress);
    }

    /**
     * @notice Set the max number of concurrent InProgress proposals
     * @dev Only callable by self via _executeTransaction
     * @param _newMaxInProgressProposals - new value for maxInProgressProposals
     */
    function setMaxInProgressProposals(uint16 _newMaxInProgressProposals) external {
        _requireIsInitialized();

        require(msg.sender == address(this), ERROR_ONLY_GOVERNANCE);
        require(
            _newMaxInProgressProposals > 0,
            "Governance: Requires non-zero _newMaxInProgressProposals"
        );
        maxInProgressProposals = _newMaxInProgressProposals;
        emit MaxInProgressProposalsUpdated(_newMaxInProgressProposals);
    }

    /**
     * @notice Set the execution delay for a proposal
     * @dev Only callable by self via _executeTransaction
     * @param _newExecutionDelay - new value for executionDelay
     */
    function setExecutionDelay(uint256 _newExecutionDelay) external {
        _requireIsInitialized();

        require(msg.sender == address(this), ERROR_ONLY_GOVERNANCE);
        // executionDelay does not have to be non-zero
        executionDelay = _newExecutionDelay;
        emit ExecutionDelayUpdated(_newExecutionDelay);
    }

    // ========================================= Guardian Actions =========================================

    /**
     * @notice Allows the guardianAddress to execute protocol actions
     * @param _targetContractRegistryKey - key in registry of target contract
     * @param _callValue - amount of wei if a token transfer is involved
     * @param _functionSignature - function signature of the function to be executed if proposal is successful
     * @param _callData - encoded value(s) to call function with if proposal is successful
     */
    function guardianExecuteTransaction(
        bytes32 _targetContractRegistryKey,
        uint256 _callValue,
        string calldata _functionSignature,
        bytes calldata _callData
    ) external
    {
        _requireIsInitialized();

        require(
            msg.sender == guardianAddress,
            "Governance: Only guardian."
        );

        // _targetContractRegistryKey must point to a valid registered contract
        address targetContractAddress = registry.getContract(_targetContractRegistryKey);
        require(
            targetContractAddress != address(0x00),
            "Governance: _targetContractRegistryKey must point to valid registered contract"
        );

        // Signature cannot be empty
        require(
            bytes(_functionSignature).length != 0,
            "Governance: _functionSignature cannot be empty."
        );

        (bool success, bytes memory returnData) = _executeTransaction(
            targetContractAddress,
            _callValue,
            _functionSignature,
            _callData
        );

        require(success, "Governance: Transaction failed.");

        emit GuardianTransactionExecuted(
            targetContractAddress,
            _callValue,
            _functionSignature,
            _callData,
            returnData
        );
    }

    /**
     * @notice Change the guardian address
     * @dev Only callable by current guardian
     * @param _newGuardianAddress - new guardian address
     */
    function transferGuardianship(address _newGuardianAddress) external {
        _requireIsInitialized();

        require(
            msg.sender == guardianAddress,
            "Governance: Only guardian."
        );

        guardianAddress = _newGuardianAddress;

        emit GuardianshipTransferred(_newGuardianAddress);
    }

    // ========================================= Getter Functions =========================================

    /**
     * @notice Get proposal information by proposal Id
     * @param _proposalId - id of proposal
     */
    function getProposalById(uint256 _proposalId)
    external view returns (
        uint256 proposalId,
        address proposer,
        uint256 submissionBlockNumber,
        bytes32 targetContractRegistryKey,
        address targetContractAddress,
        uint256 callValue,
        string memory functionSignature,
        bytes memory callData,
        Outcome outcome,
        uint256 voteMagnitudeYes,
        uint256 voteMagnitudeNo,
        uint256 numVotes
    )
    {
        _requireIsInitialized();
        _requireValidProposalId(_proposalId);

        Proposal memory proposal = proposals[_proposalId];
        return (
            proposal.proposalId,
            proposal.proposer,
            proposal.submissionBlockNumber,
            proposal.targetContractRegistryKey,
            proposal.targetContractAddress,
            proposal.callValue,
            proposal.functionSignature,
            proposal.callData,
            proposal.outcome,
            proposal.voteMagnitudeYes,
            proposal.voteMagnitudeNo,
            proposal.numVotes
            /** @notice - votes mapping cannot be returned by external function */
            /** @notice - voteMagnitudes mapping cannot be returned by external function */
            /** @notice - returning contractHash leads to stack too deep compiler error, see getProposalTargetContractHash() */
        );
    }

    /**
     * @notice Get proposal target contract hash by proposalId
     * @dev This is a separate function because the getProposalById returns too many
            variables already and by adding more, you get the error
            `InternalCompilerError: Stack too deep, try using fewer variables`
     * @param _proposalId - id of proposal
     */
    function getProposalTargetContractHash(uint256 _proposalId)
    external view returns (bytes32)
    {
        _requireIsInitialized();
        _requireValidProposalId(_proposalId);

        return (proposals[_proposalId].contractHash);
    }

    /**
     * @notice Get vote direction and vote magnitude for a given proposal and voter
     * @param _proposalId - id of the proposal
     * @param _voter - address of the voter we want to check
     * @return returns vote direction and magnitude if valid vote, else default values
     */
    function getVoteInfoByProposalAndVoter(uint256 _proposalId, address _voter)
    external view returns (Vote vote, uint256 voteMagnitude)
    {
        _requireIsInitialized();
        _requireValidProposalId(_proposalId);

        return (
            proposals[_proposalId].votes[_voter],
            proposals[_proposalId].voteMagnitudes[_voter]
        );
    }

    /// @notice Get the contract Guardian address
    function getGuardianAddress() external view returns (address) {
        _requireIsInitialized();

        return guardianAddress;
    }

    /// @notice Get the Staking address
    function getStakingAddress() external view returns (address) {
        _requireIsInitialized();

        return stakingAddress;
    }

    /// @notice Get the ServiceProviderFactory address
    function getServiceProviderFactoryAddress() external view returns (address) {
        _requireIsInitialized();

        return serviceProviderFactoryAddress;
    }

    /// @notice Get the DelegateManager address
    function getDelegateManagerAddress() external view returns (address) {
        _requireIsInitialized();

        return delegateManagerAddress;
    }

    /// @notice Get the contract voting period
    function getVotingPeriod() external view returns (uint256) {
        _requireIsInitialized();

        return votingPeriod;
    }

    /// @notice Get the contract voting quorum percent
    function getVotingQuorumPercent() external view returns (uint256) {
        _requireIsInitialized();

        return votingQuorumPercent;
    }

    /// @notice Get the registry address
    function getRegistryAddress() external view returns (address) {
        _requireIsInitialized();

        return address(registry);
    }

    /// @notice Used to check if is governance contract before setting governance address in other contracts
    function isGovernanceAddress() external pure returns (bool) {
        return true;
    }

    /// @notice Get the max number of concurrent InProgress proposals
    function getMaxInProgressProposals() external view returns (uint16) {
        _requireIsInitialized();

        return maxInProgressProposals;
    }

    /// @notice Get the proposal execution delay
    function getExecutionDelay() external view returns (uint256) {
        _requireIsInitialized();

        return executionDelay;
    }

    /// @notice Get the array of all InProgress proposal Ids
    function getInProgressProposals() external view returns (uint256[] memory) {
        _requireIsInitialized();

        return inProgressProposals;
    }

    /**
     * @notice Returns false if any proposals in inProgressProposals array are evaluatable
     *          Evaluatable = proposals with closed votingPeriod
     * @dev Is public since its called internally in `submitProposal()` as well as externally in UI
     */
    function inProgressProposalsAreUpToDate() external view returns (bool) {
        _requireIsInitialized();

        // compare current block number against endBlockNumber of each proposal
        for (uint256 i = 0; i < inProgressProposals.length; i++) {
            if (
                block.number >
                (proposals[inProgressProposals[i]].submissionBlockNumber).add(votingPeriod).add(executionDelay)
            ) {
                return false;
            }
        }

        return true;
    }

    // ========================================= Internal Functions =========================================

    /**
     * @notice Execute a transaction attached to a governance proposal
     * @dev We are aware of both potential re-entrancy issues and the risks associated with low-level solidity
     *      function calls here, but have chosen to keep this code with those issues in mind. All governance
     *      proposals go through a voting process, and all will be reviewed carefully to ensure that they
     *      adhere to the expected behaviors of this call - but adding restrictions here would limit the ability
     *      of the governance system to do required work in a generic way.
     * @param _targetContractAddress - address of registry proxy contract to execute transaction on
     * @param _callValue - amount of wei if a token transfer is involved
     * @param _functionSignature - function signature of the function to be executed if proposal is successful
     * @param _callData - encoded value(s) to call function with if proposal is successful
     */
    function _executeTransaction(
        address _targetContractAddress,
        uint256 _callValue,
        string memory _functionSignature,
        bytes memory _callData
    ) internal returns (bool success, bytes memory returnData)
    {
        bytes memory encodedCallData = abi.encodePacked(
            bytes4(keccak256(bytes(_functionSignature))),
            _callData
        );
        (success, returnData) = (
            // solium-disable-next-line security/no-call-value
            _targetContractAddress.call.value(_callValue)(encodedCallData)
        );

        return (success, returnData);
    }

    function _increaseVoteMagnitudeYes(uint256 _proposalId, uint256 _voterStake) internal {
        proposals[_proposalId].voteMagnitudeYes = (
            proposals[_proposalId].voteMagnitudeYes.add(_voterStake)
        );
    }

    function _increaseVoteMagnitudeNo(uint256 _proposalId, uint256 _voterStake) internal {
        proposals[_proposalId].voteMagnitudeNo = (
            proposals[_proposalId].voteMagnitudeNo.add(_voterStake)
        );
    }

    function _decreaseVoteMagnitudeYes(uint256 _proposalId, uint256 _voterStake) internal {
        proposals[_proposalId].voteMagnitudeYes = (
            proposals[_proposalId].voteMagnitudeYes.sub(_voterStake)
        );
    }

    function _decreaseVoteMagnitudeNo(uint256 _proposalId, uint256 _voterStake) internal {
        proposals[_proposalId].voteMagnitudeNo = (
            proposals[_proposalId].voteMagnitudeNo.sub(_voterStake)
        );
    }

    /**
     * @dev Can make O(1) by storing index pointer in proposals mapping.
     *      Requires inProgressProposals to be 1-indexed, since all proposals that are not present
     *          will have pointer set to 0.
     */
    function _removeFromInProgressProposals(uint256 _proposalId) internal {
        uint256 index = 0;
        for (uint256 i = 0; i < inProgressProposals.length; i++) {
            if (inProgressProposals[i] == _proposalId) {
                index = i;
                break;
            }
        }

        // Swap proposalId to end of array + pop (deletes last elem + decrements array length)
        inProgressProposals[index] = inProgressProposals[inProgressProposals.length - 1];
        inProgressProposals.pop();
    }

    /**
     * @notice Returns true if voting quorum percentage met for proposal, else false.
     * @dev Quorum is met if total voteMagnitude * 100 / total active stake in Staking
     * @dev Eventual multiplication overflow:
     *      (proposal.voteMagnitudeYes + proposal.voteMagnitudeNo), with 100% staking participation,
     *          can sum to at most the entire token supply of 10^27
     *      With 7% annual token supply inflation, multiplication can overflow ~1635 years at the earliest:
     *      log(2^256/(10^27*100))/log(1.07) ~= 1635
     *
     * @dev Note that quorum is evaluated based on total staked at proposal submission
     *      not total staked at proposal evaluation, this is expected behavior
     */
    function _quorumMet(Proposal memory proposal, Staking stakingContract)
    internal view returns (bool)
    {
        uint256 participation = (
            (proposal.voteMagnitudeYes + proposal.voteMagnitudeNo)
            .mul(100)
            .div(stakingContract.totalStakedAt(proposal.submissionBlockNumber))
        );
        return participation >= votingQuorumPercent;
    }

    // ========================================= Private Functions =========================================

    function _requireStakingAddressIsSet() private view {
        require(
            stakingAddress != address(0x00),
            "Governance: stakingAddress is not set"
        );
    }

    function _requireServiceProviderFactoryAddressIsSet() private view {
        require(
            serviceProviderFactoryAddress != address(0x00),
            "Governance: serviceProviderFactoryAddress is not set"
        );
    }

    function _requireDelegateManagerAddressIsSet() private view {
        require(
            delegateManagerAddress != address(0x00),
            "Governance: delegateManagerAddress is not set"
        );
    }

    function _requireValidProposalId(uint256 _proposalId) private view {
        require(
            _proposalId <= lastProposalId && _proposalId > 0,
            "Governance: Must provide valid non-zero _proposalId"
        );
    }

    /**
     * Calculates and returns active stake for address
     *
     * Active stake = (active deployer stake + active delegator stake)
     *      active deployer stake = (direct deployer stake - locked deployer stake)
     *          locked deployer stake = amount of pending decreaseStakeRequest for address
     *      active delegator stake = (total delegator stake - locked delegator stake)
     *          locked delegator stake = amount of pending undelegateRequest for address
     */
    function _calculateAddressActiveStake(address _address) private view returns (uint256) {
        ServiceProviderFactory spFactory = ServiceProviderFactory(serviceProviderFactoryAddress);
        DelegateManager delegateManager = DelegateManager(delegateManagerAddress);

        // Amount directly staked by address, if any, in ServiceProviderFactory
        (uint256 directDeployerStake,,,,,) = spFactory.getServiceProviderDetails(_address);
        // Amount of pending decreasedStakeRequest for address, if any, in ServiceProviderFactory
        (uint256 lockedDeployerStake,) = spFactory.getPendingDecreaseStakeRequest(_address);
        // active deployer stake = (direct deployer stake - locked deployer stake)
        uint256 activeDeployerStake = directDeployerStake.sub(lockedDeployerStake);

        // Total amount delegated by address, if any, in DelegateManager
        uint256 totalDelegatorStake = delegateManager.getTotalDelegatorStake(_address);
        // Amount of pending undelegateRequest for address, if any, in DelegateManager
        (,uint256 lockedDelegatorStake, ) = delegateManager.getPendingUndelegateRequest(_address);
        // active delegator stake = (total delegator stake - locked delegator stake)
        uint256 activeDelegatorStake = totalDelegatorStake.sub(lockedDelegatorStake);

        // activeStake = (activeDeployerStake + activeDelegatorStake)
        uint256 activeStake = activeDeployerStake.add(activeDelegatorStake);

        return activeStake;
    }

    // solium-disable security/no-inline-assembly
    /**
     * @notice Helper function to generate the code hash for a contract address
     * @return contract code hash
     */
    function _getCodeHash(address _contract) private view returns (bytes32) {
        bytes32 contractHash;
        assembly {
          contractHash := extcodehash(_contract)
        }
        return contractHash;
    }
}

// File: contracts/Staking.sol

pragma solidity ^0.5.0;











contract Staking is InitializableV2 {
    using SafeMath for uint256;
    using Uint256Helpers for uint256;
    using Checkpointing for Checkpointing.History;
    using SafeERC20 for ERC20;

    string private constant ERROR_TOKEN_NOT_CONTRACT = "Staking: Staking token is not a contract";
    string private constant ERROR_AMOUNT_ZERO = "Staking: Zero amount not allowed";
    string private constant ERROR_ONLY_GOVERNANCE = "Staking: Only governance";
    string private constant ERROR_ONLY_DELEGATE_MANAGER = (
      "Staking: Only callable from DelegateManager"
    );
    string private constant ERROR_ONLY_SERVICE_PROVIDER_FACTORY = (
      "Staking: Only callable from ServiceProviderFactory"
    );

    address private governanceAddress;
    address private claimsManagerAddress;
    address private delegateManagerAddress;
    address private serviceProviderFactoryAddress;

    /// @dev stores the history of staking and claims for a given address
    struct Account {
        Checkpointing.History stakedHistory;
        Checkpointing.History claimHistory;
    }

    /// @dev ERC-20 token that will be used to stake with
    ERC20 internal stakingToken;

    /// @dev maps addresses to staking and claims history
    mapping (address => Account) internal accounts;

    /// @dev total staked tokens at a given block
    Checkpointing.History internal totalStakedHistory;

    event Staked(address indexed user, uint256 amount, uint256 total);
    event Unstaked(address indexed user, uint256 amount, uint256 total);
    event Slashed(address indexed user, uint256 amount, uint256 total);

    /**
     * @notice Function to initialize the contract
     * @dev claimsManagerAddress must be initialized separately after ClaimsManager contract is deployed
     * @dev delegateManagerAddress must be initialized separately after DelegateManager contract is deployed
     * @dev serviceProviderFactoryAddress must be initialized separately after ServiceProviderFactory contract is deployed
     * @param _tokenAddress - address of ERC20 token that will be staked
     * @param _governanceAddress - address for Governance proxy contract
     */
    function initialize(
        address _tokenAddress,
        address _governanceAddress
    ) public initializer
    {
        require(Address.isContract(_tokenAddress), ERROR_TOKEN_NOT_CONTRACT);
        stakingToken = ERC20(_tokenAddress);
        _updateGovernanceAddress(_governanceAddress);
        InitializableV2.initialize();
    }

    /**
     * @notice Set the Governance address
     * @dev Only callable by Governance address
     * @param _governanceAddress - address for new Governance contract
     */
    function setGovernanceAddress(address _governanceAddress) external {
        _requireIsInitialized();

        require(msg.sender == governanceAddress, ERROR_ONLY_GOVERNANCE);
        _updateGovernanceAddress(_governanceAddress);
    }

    /**
     * @notice Set the ClaimsManaager address
     * @dev Only callable by Governance address
     * @param _claimsManager - address for new ClaimsManaager contract
     */
    function setClaimsManagerAddress(address _claimsManager) external {
        _requireIsInitialized();

        require(msg.sender == governanceAddress, ERROR_ONLY_GOVERNANCE);
        claimsManagerAddress = _claimsManager;
    }

    /**
     * @notice Set the ServiceProviderFactory address
     * @dev Only callable by Governance address
     * @param _spFactory - address for new ServiceProviderFactory contract
     */
    function setServiceProviderFactoryAddress(address _spFactory) external {
        _requireIsInitialized();

        require(msg.sender == governanceAddress, ERROR_ONLY_GOVERNANCE);
        serviceProviderFactoryAddress = _spFactory;
    }

    /**
     * @notice Set the DelegateManager address
     * @dev Only callable by Governance address
     * @param _delegateManager - address for new DelegateManager contract
     */
    function setDelegateManagerAddress(address _delegateManager) external {
        _requireIsInitialized();

        require(msg.sender == governanceAddress, ERROR_ONLY_GOVERNANCE);
        delegateManagerAddress = _delegateManager;
    }

    /* External functions */

    /**
     * @notice Funds `_amount` of tokens from ClaimsManager to target account
     * @param _amount - amount of rewards to  add to stake
     * @param _stakerAccount - address of staker
     */
    function stakeRewards(uint256 _amount, address _stakerAccount) external {
        _requireIsInitialized();
        _requireClaimsManagerAddressIsSet();

        require(
            msg.sender == claimsManagerAddress,
            "Staking: Only callable from ClaimsManager"
        );
        _stakeFor(_stakerAccount, msg.sender, _amount);

        this.updateClaimHistory(_amount, _stakerAccount);
    }

    /**
     * @notice Update claim history by adding an event to the claim history
     * @param _amount - amount to add to claim history
     * @param _stakerAccount - address of staker
     */
    function updateClaimHistory(uint256 _amount, address _stakerAccount) external {
        _requireIsInitialized();
        _requireClaimsManagerAddressIsSet();

        require(
            msg.sender == claimsManagerAddress || msg.sender == address(this),
            "Staking: Only callable from ClaimsManager or Staking.sol"
        );

        // Update claim history even if no value claimed
        accounts[_stakerAccount].claimHistory.add(block.number.toUint64(), _amount);
    }

    /**
     * @notice Slashes `_amount` tokens from _slashAddress
     * @dev Callable from DelegateManager
     * @param _amount - Number of tokens slashed
     * @param _slashAddress - Address being slashed
     */
    function slash(
        uint256 _amount,
        address _slashAddress
    ) external
    {
        _requireIsInitialized();
        _requireDelegateManagerAddressIsSet();

        require(
            msg.sender == delegateManagerAddress,
            ERROR_ONLY_DELEGATE_MANAGER
        );

        // Burn slashed tokens from account
        _burnFor(_slashAddress, _amount);

        emit Slashed(
            _slashAddress,
            _amount,
            totalStakedFor(_slashAddress)
        );
    }

    /**
     * @notice Stakes `_amount` tokens, transferring them from _accountAddress, and assigns them to `_accountAddress`
     * @param _accountAddress - The final staker of the tokens
     * @param _amount - Number of tokens staked
     */
    function stakeFor(
        address _accountAddress,
        uint256 _amount
    ) external
    {
        _requireIsInitialized();
        _requireServiceProviderFactoryAddressIsSet();

        require(
            msg.sender == serviceProviderFactoryAddress,
            ERROR_ONLY_SERVICE_PROVIDER_FACTORY
        );
        _stakeFor(
            _accountAddress,
            _accountAddress,
            _amount
        );
    }

    /**
     * @notice Unstakes `_amount` tokens, returning them to the desired account.
     * @param _accountAddress - Account unstaked for, and token recipient
     * @param _amount - Number of tokens staked
     */
    function unstakeFor(
        address _accountAddress,
        uint256 _amount
    ) external
    {
        _requireIsInitialized();
        _requireServiceProviderFactoryAddressIsSet();

        require(
            msg.sender == serviceProviderFactoryAddress,
            ERROR_ONLY_SERVICE_PROVIDER_FACTORY
        );
        _unstakeFor(
            _accountAddress,
            _accountAddress,
            _amount
        );
    }

    /**
     * @notice Stakes `_amount` tokens, transferring them from `_delegatorAddress` to `_accountAddress`,
               only callable by DelegateManager
     * @param _accountAddress - The final staker of the tokens
     * @param _delegatorAddress - Address from which to transfer tokens
     * @param _amount - Number of tokens staked
     */
    function delegateStakeFor(
        address _accountAddress,
        address _delegatorAddress,
        uint256 _amount
    ) external {
        _requireIsInitialized();
        _requireDelegateManagerAddressIsSet();

        require(
            msg.sender == delegateManagerAddress,
            ERROR_ONLY_DELEGATE_MANAGER
        );
        _stakeFor(
            _accountAddress,
            _delegatorAddress,
            _amount);
    }

    /**
     * @notice Unstakes '_amount` tokens, transferring them from `_accountAddress` to `_delegatorAddress`,
               only callable by DelegateManager
     * @param _accountAddress - The staker of the tokens
     * @param _delegatorAddress - Address from which to transfer tokens
     * @param _amount - Number of tokens unstaked
     */
    function undelegateStakeFor(
        address _accountAddress,
        address _delegatorAddress,
        uint256 _amount
    ) external {
        _requireIsInitialized();
        _requireDelegateManagerAddressIsSet();

        require(
            msg.sender == delegateManagerAddress,
            ERROR_ONLY_DELEGATE_MANAGER
        );
        _unstakeFor(
            _accountAddress,
            _delegatorAddress,
            _amount);
    }

    /**
     * @notice Get the token used by the contract for staking and locking
     * @return The token used by the contract for staking and locking
     */
    function token() external view returns (address) {
        _requireIsInitialized();

        return address(stakingToken);
    }

    /**
     * @notice Check whether it supports history of stakes
     * @return Always true
     */
    function supportsHistory() external view returns (bool) {
        _requireIsInitialized();

        return true;
    }

    /**
     * @notice Get last time `_accountAddress` modified its staked balance
     * @param _accountAddress - Account requesting for
     * @return Last block number when account's balance was modified
     */
    function lastStakedFor(address _accountAddress) external view returns (uint256) {
        _requireIsInitialized();

        uint256 length = accounts[_accountAddress].stakedHistory.history.length;
        if (length > 0) {
            return uint256(accounts[_accountAddress].stakedHistory.history[length - 1].time);
        }
        return 0;
    }

    /**
     * @notice Get last time `_accountAddress` claimed a staking reward
     * @param _accountAddress - Account requesting for
     * @return Last block number when claim requested
     */
    function lastClaimedFor(address _accountAddress) external view returns (uint256) {
        _requireIsInitialized();

        uint256 length = accounts[_accountAddress].claimHistory.history.length;
        if (length > 0) {
            return uint256(accounts[_accountAddress].claimHistory.history[length - 1].time);
        }
        return 0;
    }

    /**
     * @notice Get the total amount of tokens staked by `_accountAddress` at block number `_blockNumber`
     * @param _accountAddress - Account requesting for
     * @param _blockNumber - Block number at which we are requesting
     * @return The amount of tokens staked by the account at the given block number
     */
    function totalStakedForAt(
        address _accountAddress,
        uint256 _blockNumber
    ) external view returns (uint256) {
        _requireIsInitialized();

        return accounts[_accountAddress].stakedHistory.get(_blockNumber.toUint64());
    }

    /**
     * @notice Get the total amount of tokens staked by all users at block number `_blockNumber`
     * @param _blockNumber - Block number at which we are requesting
     * @return The amount of tokens staked at the given block number
     */
    function totalStakedAt(uint256 _blockNumber) external view returns (uint256) {
        _requireIsInitialized();

        return totalStakedHistory.get(_blockNumber.toUint64());
    }

    /// @notice Get the Governance address
    function getGovernanceAddress() external view returns (address) {
        _requireIsInitialized();

        return governanceAddress;
    }

    /// @notice Get the ClaimsManager address
    function getClaimsManagerAddress() external view returns (address) {
        _requireIsInitialized();

        return claimsManagerAddress;
    }

    /// @notice Get the ServiceProviderFactory address
    function getServiceProviderFactoryAddress() external view returns (address) {
        _requireIsInitialized();

        return serviceProviderFactoryAddress;
    }

    /// @notice Get the DelegateManager address
    function getDelegateManagerAddress() external view returns (address) {
        _requireIsInitialized();

        return delegateManagerAddress;
    }

    /**
     * @notice Helper function wrapped around totalStakedFor. Checks whether _accountAddress
            is currently a valid staker with a non-zero stake
     * @param _accountAddress - Account requesting for
     * @return Boolean indicating whether account is a staker
     */
    function isStaker(address _accountAddress) external view returns (bool) {
        _requireIsInitialized();

        return totalStakedFor(_accountAddress) > 0;
    }

    /* Public functions */

    /**
     * @notice Get the amount of tokens staked by `_accountAddress`
     * @param _accountAddress - The owner of the tokens
     * @return The amount of tokens staked by the given account
     */
    function totalStakedFor(address _accountAddress) public view returns (uint256) {
        _requireIsInitialized();

        // we assume it's not possible to stake in the future
        return accounts[_accountAddress].stakedHistory.getLast();
    }

    /**
     * @notice Get the total amount of tokens staked by all users
     * @return The total amount of tokens staked by all users
     */
    function totalStaked() public view returns (uint256) {
        _requireIsInitialized();

        // we assume it's not possible to stake in the future
        return totalStakedHistory.getLast();
    }

    // ========================================= Internal Functions =========================================

    /**
     * @notice Adds stake from a transfer account to the stake account
     * @param _stakeAccount - Account that funds will be staked for
     * @param _transferAccount - Account that funds will be transferred from
     * @param _amount - amount to stake
     */
    function _stakeFor(
        address _stakeAccount,
        address _transferAccount,
        uint256 _amount
    ) internal
    {
        // staking 0 tokens is invalid
        require(_amount > 0, ERROR_AMOUNT_ZERO);

        // Checkpoint updated staking balance
        _modifyStakeBalance(_stakeAccount, _amount, true);

        // checkpoint total supply
        _modifyTotalStaked(_amount, true);

        // pull tokens into Staking contract
        stakingToken.safeTransferFrom(_transferAccount, address(this), _amount);

        emit Staked(
            _stakeAccount,
            _amount,
            totalStakedFor(_stakeAccount));
    }

    /**
     * @notice Unstakes tokens from a stake account to a transfer account
     * @param _stakeAccount - Account that staked funds will be transferred from
     * @param _transferAccount - Account that funds will be transferred to
     * @param _amount - amount to unstake
     */
    function _unstakeFor(
        address _stakeAccount,
        address _transferAccount,
        uint256 _amount
    ) internal
    {
        require(_amount > 0, ERROR_AMOUNT_ZERO);

        // checkpoint updated staking balance
        _modifyStakeBalance(_stakeAccount, _amount, false);

        // checkpoint total supply
        _modifyTotalStaked(_amount, false);

        // transfer tokens
        stakingToken.safeTransfer(_transferAccount, _amount);

        emit Unstaked(
            _stakeAccount,
            _amount,
            totalStakedFor(_stakeAccount)
        );
    }

    /**
     * @notice Burn tokens for a given staker
     * @dev Called when slash occurs
     * @param _stakeAccount - Account for which funds will be burned
     * @param _amount - amount to burn
     */
    function _burnFor(address _stakeAccount, uint256 _amount) internal {
        // burning zero tokens is not allowed
        require(_amount > 0, ERROR_AMOUNT_ZERO);

        // checkpoint updated staking balance
        _modifyStakeBalance(_stakeAccount, _amount, false);

        // checkpoint total supply
        _modifyTotalStaked(_amount, false);

        // burn
        ERC20Burnable(address(stakingToken)).burn(_amount);

        /** No event emitted since token.burn() call already emits a Transfer event */
    }

    /**
     * @notice Increase or decrease the staked balance for an account
     * @param _accountAddress - Account to modify
     * @param _by - amount to modify
     * @param _increase - true if increase in stake, false if decrease
     */
    function _modifyStakeBalance(address _accountAddress, uint256 _by, bool _increase) internal {
        uint256 currentInternalStake = accounts[_accountAddress].stakedHistory.getLast();

        uint256 newStake;
        if (_increase) {
            newStake = currentInternalStake.add(_by);
        } else {
            require(
                currentInternalStake >= _by,
                "Staking: Cannot decrease greater than current balance");
            newStake = currentInternalStake.sub(_by);
        }

        // add new value to account history
        accounts[_accountAddress].stakedHistory.add(block.number.toUint64(), newStake);
    }

    /**
     * @notice Increase or decrease the staked balance across all accounts
     * @param _by - amount to modify
     * @param _increase - true if increase in stake, false if decrease
     */
    function _modifyTotalStaked(uint256 _by, bool _increase) internal {
        uint256 currentStake = totalStaked();

        uint256 newStake;
        if (_increase) {
            newStake = currentStake.add(_by);
        } else {
            newStake = currentStake.sub(_by);
        }

        // add new value to total history
        totalStakedHistory.add(block.number.toUint64(), newStake);
    }

    /**
     * @notice Set the governance address after confirming contract identity
     * @param _governanceAddress - Incoming governance address
     */
    function _updateGovernanceAddress(address _governanceAddress) internal {
        require(
            Governance(_governanceAddress).isGovernanceAddress() == true,
            "Staking: _governanceAddress is not a valid governance contract"
        );
        governanceAddress = _governanceAddress;
    }

    // ========================================= Private Functions =========================================

    function _requireClaimsManagerAddressIsSet() private view {
        require(claimsManagerAddress != address(0x00), "Staking: claimsManagerAddress is not set");
    }

    function _requireDelegateManagerAddressIsSet() private view {
        require(
            delegateManagerAddress != address(0x00),
            "Staking: delegateManagerAddress is not set"
        );
    }

    function _requireServiceProviderFactoryAddressIsSet() private view {
        require(
            serviceProviderFactoryAddress != address(0x00),
            "Staking: serviceProviderFactoryAddress is not set"
        );
    }

}

// File: contracts/ServiceTypeManager.sol

pragma solidity ^0.5.0;




contract ServiceTypeManager is InitializableV2 {
    address governanceAddress;

    string private constant ERROR_ONLY_GOVERNANCE = (
        "ServiceTypeManager: Only callable by Governance contract"
    );

    /**
     * @dev - mapping of serviceType - serviceTypeVersion
     * Example - "discovery-provider" - ["0.0.1", "0.0.2", ..., "currentVersion"]
     */
    mapping(bytes32 => bytes32[]) private serviceTypeVersions;

    /**
     * @dev - mapping of serviceType - < serviceTypeVersion, isValid >
     * Example - "discovery-provider" - <"0.0.1", true>
     */
    mapping(bytes32 => mapping(bytes32 => bool)) private serviceTypeVersionInfo;

    /// @dev List of valid service types
    bytes32[] private validServiceTypes;

    /// @dev Struct representing service type info
    struct ServiceTypeInfo {
        bool isValid;
        uint256 minStake;
        uint256 maxStake;
    }

    /// @dev mapping of service type info
    mapping(bytes32 => ServiceTypeInfo) private serviceTypeInfo;

    event SetServiceVersion(
        bytes32 indexed _serviceType,
        bytes32 indexed _serviceVersion
    );

    event ServiceTypeAdded(
        bytes32 indexed _serviceType,
        uint256 indexed _serviceTypeMin,
        uint256 indexed _serviceTypeMax
    );

    event ServiceTypeRemoved(bytes32 indexed _serviceType);

    /**
     * @notice Function to initialize the contract
     * @param _governanceAddress - Governance proxy address
     */
    function initialize(address _governanceAddress) public initializer
    {
        _updateGovernanceAddress(_governanceAddress);
        InitializableV2.initialize();
    }

    /// @notice Get the Governance address
    function getGovernanceAddress() external view returns (address) {
        _requireIsInitialized();

        return governanceAddress;
    }

    /**
     * @notice Set the Governance address
     * @dev Only callable by Governance address
     * @param _governanceAddress - address for new Governance contract
     */
    function setGovernanceAddress(address _governanceAddress) external {
        _requireIsInitialized();

        require(msg.sender == governanceAddress, ERROR_ONLY_GOVERNANCE);
        _updateGovernanceAddress(_governanceAddress);
    }

    // ========================================= Service Type Logic =========================================

    /**
     * @notice Add a new service type
     * @param _serviceType - type of service to add
     * @param _serviceTypeMin - minimum stake for service type
     * @param _serviceTypeMax - maximum stake for service type
     */
    function addServiceType(
        bytes32 _serviceType,
        uint256 _serviceTypeMin,
        uint256 _serviceTypeMax
    ) external
    {
        _requireIsInitialized();

        require(msg.sender == governanceAddress, ERROR_ONLY_GOVERNANCE);
        require(
            !this.serviceTypeIsValid(_serviceType),
            "ServiceTypeManager: Already known service type"
        );
        require(
            _serviceTypeMax > _serviceTypeMin,
            "ServiceTypeManager: Max stake must be non-zero and greater than min stake"
        );

        // Ensure serviceType cannot be re-added if it previously existed and was removed
        // stored maxStake > 0 means it was previously added and removed
        require(
            serviceTypeInfo[_serviceType].maxStake == 0,
            "ServiceTypeManager: Cannot re-add serviceType after it was removed."
        );

        validServiceTypes.push(_serviceType);
        serviceTypeInfo[_serviceType] = ServiceTypeInfo({
            isValid: true,
            minStake: _serviceTypeMin,
            maxStake: _serviceTypeMax
        });

        emit ServiceTypeAdded(_serviceType, _serviceTypeMin, _serviceTypeMax);
    }

    /**
     * @notice Remove an existing service type
     * @param _serviceType - name of service type to remove
     */
    function removeServiceType(bytes32 _serviceType) external {
        _requireIsInitialized();

        require(msg.sender == governanceAddress, ERROR_ONLY_GOVERNANCE);

        uint256 serviceIndex = 0;
        bool foundService = false;
        for (uint256 i = 0; i < validServiceTypes.length; i ++) {
            if (validServiceTypes[i] == _serviceType) {
                serviceIndex = i;
                foundService = true;
                break;
            }
        }
        require(foundService == true, "ServiceTypeManager: Invalid service type, not found");
        // Overwrite service index
        uint256 lastIndex = validServiceTypes.length - 1;
        validServiceTypes[serviceIndex] = validServiceTypes[lastIndex];
        validServiceTypes.length--;

        // Mark as invalid
        serviceTypeInfo[_serviceType].isValid = false;
        // Note - stake bounds are not reset so they can be checked to prevent serviceType from being re-added
        emit ServiceTypeRemoved(_serviceType);
    }

    /**
     * @notice Get isValid, min and max stake for a given service type
     * @param _serviceType - type of service
     * @return isValid, min and max stake for type
     */
    function getServiceTypeInfo(bytes32 _serviceType)
    external view returns (bool isValid, uint256 minStake, uint256 maxStake)
    {
        _requireIsInitialized();

        return (
            serviceTypeInfo[_serviceType].isValid,
            serviceTypeInfo[_serviceType].minStake,
            serviceTypeInfo[_serviceType].maxStake
        );
    }

    /**
     * @notice Get list of valid service types
     */
    function getValidServiceTypes()
    external view returns (bytes32[] memory)
    {
        _requireIsInitialized();

        return validServiceTypes;
    }

    /**
     * @notice Return indicating whether this is a valid service type
     */
    function serviceTypeIsValid(bytes32 _serviceType)
    external view returns (bool)
    {
        _requireIsInitialized();

        return serviceTypeInfo[_serviceType].isValid;
    }

    // ========================================= Service Version Logic =========================================

    /**
     * @notice Add new version for a serviceType
     * @param _serviceType - type of service
     * @param _serviceVersion - new version of service to add
     */
    function setServiceVersion(
        bytes32 _serviceType,
        bytes32 _serviceVersion
    ) external
    {
        _requireIsInitialized();

        require(msg.sender == governanceAddress, ERROR_ONLY_GOVERNANCE);
        require(this.serviceTypeIsValid(_serviceType), "ServiceTypeManager: Invalid service type");
        require(
            serviceTypeVersionInfo[_serviceType][_serviceVersion] == false,
            "ServiceTypeManager: Already registered"
        );

         // Update array of known versions for type
        serviceTypeVersions[_serviceType].push(_serviceVersion);

        // Update status for this specific service version
        serviceTypeVersionInfo[_serviceType][_serviceVersion] = true;

        emit SetServiceVersion(_serviceType, _serviceVersion);
    }

    /**
     * @notice Get a version for a service type given it's index
     * @param _serviceType - type of service
     * @param _versionIndex - index in list of service versions
     * @return bytes32 value for serviceVersion
     */
    function getVersion(bytes32 _serviceType, uint256 _versionIndex)
    external view returns (bytes32)
    {
        _requireIsInitialized();

        require(
            serviceTypeVersions[_serviceType].length > _versionIndex,
            "ServiceTypeManager: No registered version of serviceType"
        );
        return (serviceTypeVersions[_serviceType][_versionIndex]);
    }

    /**
     * @notice Get curent version for a service type
     * @param _serviceType - type of service
     * @return Returns current version of service
     */
    function getCurrentVersion(bytes32 _serviceType)
    external view returns (bytes32)
    {
        _requireIsInitialized();

        require(
            serviceTypeVersions[_serviceType].length >= 1,
            "ServiceTypeManager: No registered version of serviceType"
        );
        uint256 latestVersionIndex = serviceTypeVersions[_serviceType].length - 1;
        return (serviceTypeVersions[_serviceType][latestVersionIndex]);
    }

    /**
     * @notice Get total number of versions for a service type
     * @param _serviceType - type of service
     */
    function getNumberOfVersions(bytes32 _serviceType)
    external view returns (uint256)
    {
        _requireIsInitialized();

        return serviceTypeVersions[_serviceType].length;
    }

    /**
     * @notice Return boolean indicating whether given version is valid for given type
     * @param _serviceType - type of service
     * @param _serviceVersion - version of service to check
     */
    function serviceVersionIsValid(bytes32 _serviceType, bytes32 _serviceVersion)
    external view returns (bool)
    {
        _requireIsInitialized();

        return serviceTypeVersionInfo[_serviceType][_serviceVersion];
    }

    /**
     * @notice Set the governance address after confirming contract identity
     * @param _governanceAddress - Incoming governance address
     */
    function _updateGovernanceAddress(address _governanceAddress) internal {
        require(
            Governance(_governanceAddress).isGovernanceAddress() == true,
            "ServiceTypeManager: _governanceAddress is not a valid governance contract"
        );
        governanceAddress = _governanceAddress;
    }
}

// File: contracts/ClaimsManager.sol

pragma solidity ^0.5.0;

/// @notice ERC20 imported via Staking.sol
/// @notice SafeERC20 imported via Staking.sol
/// @notice Governance imported via Staking.sol
/// @notice SafeMath imported via ServiceProviderFactory.sol


/**
 * Designed to automate claim funding, minting tokens as necessary
 * @notice - will call InitializableV2 constructor
 */
contract ClaimsManager is InitializableV2 {
    using SafeMath for uint256;
    using SafeERC20 for ERC20;

    string private constant ERROR_ONLY_GOVERNANCE = (
        "ClaimsManager: Only callable by Governance contract"
    );

    address private governanceAddress;
    address private stakingAddress;
    address private serviceProviderFactoryAddress;
    address private delegateManagerAddress;

    /**
      * @notice - Minimum number of blocks between funding rounds
      *       604800 seconds / week
      *       Avg block time - 13s
      *       604800 / 13 = 46523.0769231 blocks
      */
    uint256 private fundingRoundBlockDiff;

    /**
      * @notice - Configures the current funding amount per round
      *  Weekly rounds, 7% PA inflation = 70,000,000 new tokens in first year
      *                                 = 70,000,000/365*7 (year is slightly more than a week)
      *                                 = 1342465.75342 new AUDS per week
      *                                 = 1342465753420000000000000 new wei units per week
      * @dev - Past a certain block height, this schedule will be updated
      *      - Logic determining schedule will be sourced from an external contract
      */
    uint256 private fundingAmount;

    // Denotes current round
    uint256 private roundNumber;

    // Staking contract ref
    ERC20Mintable private audiusToken;

    /// @dev - Address to which recurringCommunityFundingAmount is transferred at funding round start
    address private communityPoolAddress;

    /// @dev - Reward amount transferred to communityPoolAddress at funding round start
    uint256 private recurringCommunityFundingAmount;

    // Struct representing round state
    // 1) Block at which round was funded
    // 2) Total funded for this round
    // 3) Total claimed in round
    struct Round {
        uint256 fundedBlock;
        uint256 fundedAmount;
        uint256 totalClaimedInRound;
    }

    // Current round information
    Round private currentRound;

    event RoundInitiated(
      uint256 indexed _blockNumber,
      uint256 indexed _roundNumber,
      uint256 indexed _fundAmount
    );

    event ClaimProcessed(
      address indexed _claimer,
      uint256 indexed _rewards,
      uint256 _oldTotal,
      uint256 indexed _newTotal
    );

    event CommunityRewardsTransferred(
      address indexed _transferAddress,
      uint256 indexed _amount
    );

    event FundingAmountUpdated(uint256 indexed _amount);
    event FundingRoundBlockDiffUpdated(uint256 indexed _blockDifference);
    event GovernanceAddressUpdated(address indexed _newGovernanceAddress);
    event StakingAddressUpdated(address indexed _newStakingAddress);
    event ServiceProviderFactoryAddressUpdated(address indexed _newServiceProviderFactoryAddress);
    event DelegateManagerAddressUpdated(address indexed _newDelegateManagerAddress);
    event RecurringCommunityFundingAmountUpdated(uint256 indexed _amount);
    event CommunityPoolAddressUpdated(address indexed _newCommunityPoolAddress);

    /**
     * @notice Function to initialize the contract
     * @dev stakingAddress must be initialized separately after Staking contract is deployed
     * @dev serviceProviderFactoryAddress must be initialized separately after ServiceProviderFactory contract is deployed
     * @dev delegateManagerAddress must be initialized separately after DelegateManager contract is deployed
     * @param _tokenAddress - address of ERC20 token that will be claimed
     * @param _governanceAddress - address for Governance proxy contract
     */
    function initialize(
        address _tokenAddress,
        address _governanceAddress
    ) public initializer
    {
        _updateGovernanceAddress(_governanceAddress);

        audiusToken = ERC20Mintable(_tokenAddress);

        fundingRoundBlockDiff = 46523;
        fundingAmount = 1342465753420000000000000; // 1342465.75342 AUDS
        roundNumber = 0;

        currentRound = Round({
            fundedBlock: 0,
            fundedAmount: 0,
            totalClaimedInRound: 0
        });

        // Community pool funding amount and address initialized to zero
        recurringCommunityFundingAmount = 0;
        communityPoolAddress = address(0x0);

        InitializableV2.initialize();
    }

    /// @notice Get the duration of a funding round in blocks
    function getFundingRoundBlockDiff() external view returns (uint256)
    {
        _requireIsInitialized();

        return fundingRoundBlockDiff;
    }

    /// @notice Get the last block where a funding round was initiated
    function getLastFundedBlock() external view returns (uint256)
    {
        _requireIsInitialized();

        return currentRound.fundedBlock;
    }

    /// @notice Get the amount funded per round in wei
    function getFundsPerRound() external view returns (uint256)
    {
        _requireIsInitialized();

        return fundingAmount;
    }

    /// @notice Get the total amount claimed in the current round
    function getTotalClaimedInRound() external view returns (uint256)
    {
        _requireIsInitialized();

        return currentRound.totalClaimedInRound;
    }

    /// @notice Get the Governance address
    function getGovernanceAddress() external view returns (address) {
        _requireIsInitialized();

        return governanceAddress;
    }

    /// @notice Get the ServiceProviderFactory address
    function getServiceProviderFactoryAddress() external view returns (address) {
        _requireIsInitialized();

        return serviceProviderFactoryAddress;
    }

    /// @notice Get the DelegateManager address
    function getDelegateManagerAddress() external view returns (address) {
        _requireIsInitialized();

        return delegateManagerAddress;
    }

    /**
     * @notice Get the Staking address
     */
    function getStakingAddress() external view returns (address)
    {
        _requireIsInitialized();

        return stakingAddress;
    }

    /**
     * @notice Get the community pool address
     */
    function getCommunityPoolAddress() external view returns (address)
    {
        _requireIsInitialized();

        return communityPoolAddress;
    }

    /**
     * @notice Get the community funding amount
     */
    function getRecurringCommunityFundingAmount() external view returns (uint256)
    {
        _requireIsInitialized();

        return recurringCommunityFundingAmount;
    }

    /**
     * @notice Set the Governance address
     * @dev Only callable by Governance address
     * @param _governanceAddress - address for new Governance contract
     */
    function setGovernanceAddress(address _governanceAddress) external {
        _requireIsInitialized();

        require(msg.sender == governanceAddress, ERROR_ONLY_GOVERNANCE);
        _updateGovernanceAddress(_governanceAddress);
        emit GovernanceAddressUpdated(_governanceAddress);
    }

    /**
     * @notice Set the Staking address
     * @dev Only callable by Governance address
     * @param _stakingAddress - address for new Staking contract
     */
    function setStakingAddress(address _stakingAddress) external {
        _requireIsInitialized();

        require(msg.sender == governanceAddress, ERROR_ONLY_GOVERNANCE);
        stakingAddress = _stakingAddress;
        emit StakingAddressUpdated(_stakingAddress);
    }

    /**
     * @notice Set the ServiceProviderFactory address
     * @dev Only callable by Governance address
     * @param _serviceProviderFactoryAddress - address for new ServiceProviderFactory contract
     */
    function setServiceProviderFactoryAddress(address _serviceProviderFactoryAddress) external {
        _requireIsInitialized();

        require(msg.sender == governanceAddress, ERROR_ONLY_GOVERNANCE);
        serviceProviderFactoryAddress = _serviceProviderFactoryAddress;
        emit ServiceProviderFactoryAddressUpdated(_serviceProviderFactoryAddress);
    }

    /**
     * @notice Set the DelegateManager address
     * @dev Only callable by Governance address
     * @param _delegateManagerAddress - address for new DelegateManager contract
     */
    function setDelegateManagerAddress(address _delegateManagerAddress) external {
        _requireIsInitialized();

        require(msg.sender == governanceAddress, ERROR_ONLY_GOVERNANCE);
        delegateManagerAddress = _delegateManagerAddress;
        emit DelegateManagerAddressUpdated(_delegateManagerAddress);
    }

    /**
     * @notice Start a new funding round
     * @dev Permissioned to be callable by stakers or governance contract
     */
    function initiateRound() external {
        _requireIsInitialized();
        _requireStakingAddressIsSet();

        require(
            block.number.sub(currentRound.fundedBlock) > fundingRoundBlockDiff,
            "ClaimsManager: Required block difference not met"
        );

        currentRound = Round({
            fundedBlock: block.number,
            fundedAmount: fundingAmount,
            totalClaimedInRound: 0
        });

        roundNumber = roundNumber.add(1);

        /*
         * Transfer community funding amount to community pool address, if set
         */
        if (recurringCommunityFundingAmount > 0 && communityPoolAddress != address(0x0)) {
            // ERC20Mintable always returns true
            audiusToken.mint(address(this), recurringCommunityFundingAmount);

            // Approve transfer to community pool address
            audiusToken.approve(communityPoolAddress, recurringCommunityFundingAmount);

            // Transfer to community pool address
            ERC20(address(audiusToken)).safeTransfer(communityPoolAddress, recurringCommunityFundingAmount);

            emit CommunityRewardsTransferred(communityPoolAddress, recurringCommunityFundingAmount);
        }

        emit RoundInitiated(
            currentRound.fundedBlock,
            roundNumber,
            currentRound.fundedAmount
        );
    }

    /**
     * @notice Mints and stakes tokens on behalf of ServiceProvider + delegators
     * @dev Callable through DelegateManager by Service Provider
     * @param _claimer  - service provider address
     * @param _totalLockedForSP - amount of tokens locked up across DelegateManager + ServiceProvider
     * @return minted rewards for this claimer
     */
    function processClaim(
        address _claimer,
        uint256 _totalLockedForSP
    ) external returns (uint256)
    {
        _requireIsInitialized();
        _requireStakingAddressIsSet();
        _requireDelegateManagerAddressIsSet();
        _requireServiceProviderFactoryAddressIsSet();

        require(
            msg.sender == delegateManagerAddress,
            "ClaimsManager: ProcessClaim only accessible to DelegateManager"
        );

        Staking stakingContract = Staking(stakingAddress);
        // Prevent duplicate claim
        uint256 lastUserClaimBlock = stakingContract.lastClaimedFor(_claimer);
        require(
            lastUserClaimBlock <= currentRound.fundedBlock,
            "ClaimsManager: Claim already processed for user"
        );
        uint256 totalStakedAtFundBlockForClaimer = stakingContract.totalStakedForAt(
            _claimer,
            currentRound.fundedBlock);

        (,,bool withinBounds,,,) = (
            ServiceProviderFactory(serviceProviderFactoryAddress).getServiceProviderDetails(_claimer)
        );

        // Once they claim the zero reward amount, stake can be modified once again
        // Subtract total locked amount for SP from stake at fund block
        uint256 totalActiveClaimerStake = totalStakedAtFundBlockForClaimer.sub(_totalLockedForSP);
        uint256 totalStakedAtFundBlock = stakingContract.totalStakedAt(currentRound.fundedBlock);

        // Calculate claimer rewards
        uint256 rewardsForClaimer = (
          totalActiveClaimerStake.mul(fundingAmount)
        ).div(totalStakedAtFundBlock);

        // For a claimer violating bounds, no new tokens are minted
        // Claim history is marked to zero and function is short-circuited
        // Total rewards can be zero if all stake is currently locked up
        if (!withinBounds || rewardsForClaimer == 0) {
            stakingContract.updateClaimHistory(0, _claimer);
            emit ClaimProcessed(
                _claimer,
                0,
                totalStakedAtFundBlockForClaimer,
                totalActiveClaimerStake
            );
            return 0;
        }

        // ERC20Mintable always returns true
        audiusToken.mint(address(this), rewardsForClaimer);

        // Approve transfer to staking address for claimer rewards
        // ERC20 always returns true
        audiusToken.approve(stakingAddress, rewardsForClaimer);

        // Transfer rewards
        stakingContract.stakeRewards(rewardsForClaimer, _claimer);

        // Update round claim value
        currentRound.totalClaimedInRound = currentRound.totalClaimedInRound.add(rewardsForClaimer);

        // Update round claim value
        uint256 newTotal = stakingContract.totalStakedFor(_claimer);

        emit ClaimProcessed(
            _claimer,
            rewardsForClaimer,
            totalStakedAtFundBlockForClaimer,
            newTotal
        );

        return rewardsForClaimer;
    }

    /**
     * @notice Modify funding amount per round
     * @param _newAmount - new amount to fund per round in wei
     */
    function updateFundingAmount(uint256 _newAmount) external
    {
        _requireIsInitialized();
        require(msg.sender == governanceAddress, ERROR_ONLY_GOVERNANCE);
        fundingAmount = _newAmount;
        emit FundingAmountUpdated(_newAmount);
    }

    /**
     * @notice Returns boolean indicating whether a claim is considered pending
     * @dev Note that an address with no endpoints can never have a pending claim
     * @param _sp - address of the service provider to check
     * @return true if eligible for claim, false if not
     */
    function claimPending(address _sp) external view returns (bool) {
        _requireIsInitialized();
        _requireStakingAddressIsSet();
        _requireServiceProviderFactoryAddressIsSet();

        uint256 lastClaimedForSP = Staking(stakingAddress).lastClaimedFor(_sp);
        (,,,uint256 numEndpoints,,) = (
            ServiceProviderFactory(serviceProviderFactoryAddress).getServiceProviderDetails(_sp)
        );
        return (lastClaimedForSP < currentRound.fundedBlock && numEndpoints > 0);
    }

    /**
     * @notice Modify minimum block difference between funding rounds
     * @param _newFundingRoundBlockDiff - new min block difference to set
     */
    function updateFundingRoundBlockDiff(uint256 _newFundingRoundBlockDiff) external {
        _requireIsInitialized();

        require(msg.sender == governanceAddress, ERROR_ONLY_GOVERNANCE);
        emit FundingRoundBlockDiffUpdated(_newFundingRoundBlockDiff);
        fundingRoundBlockDiff = _newFundingRoundBlockDiff;
    }

    /**
     * @notice Modify community funding amound for each round
     * @param _newRecurringCommunityFundingAmount - new reward amount transferred to
     *          communityPoolAddress at funding round start
     */
    function updateRecurringCommunityFundingAmount(
        uint256 _newRecurringCommunityFundingAmount
    ) external {
        _requireIsInitialized();

        require(msg.sender == governanceAddress, ERROR_ONLY_GOVERNANCE);
        recurringCommunityFundingAmount = _newRecurringCommunityFundingAmount;
        emit RecurringCommunityFundingAmountUpdated(_newRecurringCommunityFundingAmount);
    }

    /**
     * @notice Modify community pool address
     * @param _newCommunityPoolAddress - new address to which recurringCommunityFundingAmount
     *          is transferred at funding round start
     */
    function updateCommunityPoolAddress(address _newCommunityPoolAddress) external {
        _requireIsInitialized();

        require(msg.sender == governanceAddress, ERROR_ONLY_GOVERNANCE);
        communityPoolAddress = _newCommunityPoolAddress;
        emit CommunityPoolAddressUpdated(_newCommunityPoolAddress);
    }

    // ========================================= Private Functions =========================================

    /**
     * @notice Set the governance address after confirming contract identity
     * @param _governanceAddress - Incoming governance address
     */
    function _updateGovernanceAddress(address _governanceAddress) private {
        require(
            Governance(_governanceAddress).isGovernanceAddress() == true,
            "ClaimsManager: _governanceAddress is not a valid governance contract"
        );
        governanceAddress = _governanceAddress;
    }

    function _requireStakingAddressIsSet() private view {
        require(stakingAddress != address(0x00), "ClaimsManager: stakingAddress is not set");
    }

    function _requireDelegateManagerAddressIsSet() private view {
        require(
            delegateManagerAddress != address(0x00),
            "ClaimsManager: delegateManagerAddress is not set"
        );
    }

    function _requireServiceProviderFactoryAddressIsSet() private view {
        require(
            serviceProviderFactoryAddress != address(0x00),
            "ClaimsManager: serviceProviderFactoryAddress is not set"
        );
    }
}

// File: contracts/ServiceProviderFactory.sol

pragma solidity ^0.5.0;



/// @notice Governance imported via Staking.sol


contract ServiceProviderFactory is InitializableV2 {
    using SafeMath for uint256;

    /// @dev - denominator for deployer cut calculations
    /// @dev - user values are intended to be x/DEPLOYER_CUT_BASE
    uint256 private constant DEPLOYER_CUT_BASE = 100;

    string private constant ERROR_ONLY_GOVERNANCE = (
        "ServiceProviderFactory: Only callable by Governance contract"
    );
    string private constant ERROR_ONLY_SP_GOVERNANCE = (
        "ServiceProviderFactory: Only callable by Service Provider or Governance"
    );

    address private stakingAddress;
    address private delegateManagerAddress;
    address private governanceAddress;
    address private serviceTypeManagerAddress;
    address private claimsManagerAddress;

    /// @notice Period in blocks that a decrease stake operation is delayed.
    ///         Must be greater than governance votingPeriod + executionDelay in order to
    ///         prevent pre-emptive withdrawal in anticipation of a slash proposal
    uint256 private decreaseStakeLockupDuration;

    /// @notice Period in blocks that an update deployer cut operation is delayed.
    ///         Must be greater than funding round block diff in order
    ///         to prevent manipulation around a funding round
    uint256 private deployerCutLockupDuration;

    /// @dev - Stores following entities
    ///        1) Directly staked amount by SP, not including delegators
    ///        2) % Cut of delegator tokens taken during reward
    ///        3) Bool indicating whether this SP has met min/max requirements
    ///        4) Number of endpoints registered by SP
    ///        5) Minimum deployer stake for this service provider
    ///        6) Maximum total stake for this account
    struct ServiceProviderDetails {
        uint256 deployerStake;
        uint256 deployerCut;
        bool validBounds;
        uint256 numberOfEndpoints;
        uint256 minAccountStake;
        uint256 maxAccountStake;
    }

    /// @dev - Data structure for time delay during withdrawal
    struct DecreaseStakeRequest {
        uint256 decreaseAmount;
        uint256 lockupExpiryBlock;
    }

    /// @dev - Data structure for time delay during deployer cut update
    struct UpdateDeployerCutRequest {
        uint256 newDeployerCut;
        uint256 lockupExpiryBlock;
    }

    /// @dev - Struct maintaining information about sp
    /// @dev - blocknumber is block.number when endpoint registered
    struct ServiceEndpoint {
        address owner;
        string endpoint;
        uint256 blocknumber;
        address delegateOwnerWallet;
    }

    /// @dev - Mapping of service provider address to details
    mapping(address => ServiceProviderDetails) private spDetails;

    /// @dev - Uniquely assigned serviceProvider ID, incremented for each service type
    /// @notice - Keeps track of the total number of services registered regardless of
    ///           whether some have been deregistered since
    mapping(bytes32 => uint256) private serviceProviderTypeIDs;

    /// @dev - mapping of (serviceType -> (serviceInstanceId <-> serviceProviderInfo))
    /// @notice - stores the actual service provider data like endpoint and owner wallet
    ///           with the ability lookup by service type and service id */
    mapping(bytes32 => mapping(uint256 => ServiceEndpoint)) private serviceProviderInfo;

    /// @dev - mapping of keccak256(endpoint) to uint256 ID
    /// @notice - used to check if a endpoint has already been registered and also lookup
    /// the id of an endpoint
    mapping(bytes32 => uint256) private serviceProviderEndpointToId;

    /// @dev - mapping of address -> sp id array */
    /// @notice - stores all the services registered by a provider. for each address,
    /// provides the ability to lookup by service type and see all registered services
    mapping(address => mapping(bytes32 => uint256[])) private serviceProviderAddressToId;

    /// @dev - Mapping of service provider -> decrease stake request
    mapping(address => DecreaseStakeRequest) private decreaseStakeRequests;

    /// @dev - Mapping of service provider -> update deployer cut requests
    mapping(address => UpdateDeployerCutRequest) private updateDeployerCutRequests;

    event RegisteredServiceProvider(
      uint256 indexed _spID,
      bytes32 indexed _serviceType,
      address indexed _owner,
      string _endpoint,
      uint256 _stakeAmount
    );

    event DeregisteredServiceProvider(
      uint256 indexed _spID,
      bytes32 indexed _serviceType,
      address indexed _owner,
      string _endpoint,
      uint256 _unstakeAmount
    );

    event IncreasedStake(
      address indexed _owner,
      uint256 indexed _increaseAmount,
      uint256 indexed _newStakeAmount
    );

    event DecreaseStakeRequested(
      address indexed _owner,
      uint256 indexed _decreaseAmount,
      uint256 indexed _lockupExpiryBlock
    );

    event DecreaseStakeRequestCancelled(
      address indexed _owner,
      uint256 indexed _decreaseAmount,
      uint256 indexed _lockupExpiryBlock
    );

    event DecreaseStakeRequestEvaluated(
      address indexed _owner,
      uint256 indexed _decreaseAmount,
      uint256 indexed _newStakeAmount
    );

    event EndpointUpdated(
      bytes32 indexed _serviceType,
      address indexed _owner,
      string _oldEndpoint,
      string _newEndpoint,
      uint256 indexed _spID
    );

    event DelegateOwnerWalletUpdated(
      address indexed _owner,
      bytes32 indexed _serviceType,
      uint256 indexed _spID,
      address _updatedWallet
    );

    event DeployerCutUpdateRequested(
      address indexed _owner,
      uint256 indexed _updatedCut,
      uint256 indexed _lockupExpiryBlock
    );

    event DeployerCutUpdateRequestCancelled(
      address indexed _owner,
      uint256 indexed _requestedCut,
      uint256 indexed _finalCut
    );

    event DeployerCutUpdateRequestEvaluated(
      address indexed _owner,
      uint256 indexed _updatedCut
    );

    event DecreaseStakeLockupDurationUpdated(uint256 indexed _lockupDuration);
    event UpdateDeployerCutLockupDurationUpdated(uint256 indexed _lockupDuration);
    event GovernanceAddressUpdated(address indexed _newGovernanceAddress);
    event StakingAddressUpdated(address indexed _newStakingAddress);
    event ClaimsManagerAddressUpdated(address indexed _newClaimsManagerAddress);
    event DelegateManagerAddressUpdated(address indexed _newDelegateManagerAddress);
    event ServiceTypeManagerAddressUpdated(address indexed _newServiceTypeManagerAddress);

    /**
     * @notice Function to initialize the contract
     * @dev stakingAddress must be initialized separately after Staking contract is deployed
     * @dev delegateManagerAddress must be initialized separately after DelegateManager contract is deployed
     * @dev serviceTypeManagerAddress must be initialized separately after ServiceTypeManager contract is deployed
     * @dev claimsManagerAddress must be initialized separately after ClaimsManager contract is deployed
     * @param _governanceAddress - Governance proxy address
     */
    function initialize (
        address _governanceAddress,
        address _claimsManagerAddress,
        uint256 _decreaseStakeLockupDuration,
        uint256 _deployerCutLockupDuration
    ) public initializer
    {
        _updateGovernanceAddress(_governanceAddress);
        claimsManagerAddress = _claimsManagerAddress;
        _updateDecreaseStakeLockupDuration(_decreaseStakeLockupDuration);
        _updateDeployerCutLockupDuration(_deployerCutLockupDuration);
        InitializableV2.initialize();
    }

    /**
     * @notice Register a new endpoint to the account of msg.sender
     * @dev Transfers stake from service provider into staking pool
     * @param _serviceType - type of service to register, must be valid in ServiceTypeManager
     * @param _endpoint - url of the service to register - url of the service to register
     * @param _stakeAmount - amount to stake, must be within bounds in ServiceTypeManager
     * @param _delegateOwnerWallet - wallet to delegate some permissions for some basic management properties
     * @return New service provider ID for this endpoint
     */
    function register(
        bytes32 _serviceType,
        string calldata _endpoint,
        uint256 _stakeAmount,
        address _delegateOwnerWallet
    ) external returns (uint256)
    {
        _requireIsInitialized();
        _requireStakingAddressIsSet();
        _requireServiceTypeManagerAddressIsSet();
        _requireClaimsManagerAddressIsSet();

        require(
            ServiceTypeManager(serviceTypeManagerAddress).serviceTypeIsValid(_serviceType),
            "ServiceProviderFactory: Valid service type required");

        // Stake token amount from msg.sender
        if (_stakeAmount > 0) {
            require(
                !_claimPending(msg.sender),
                "ServiceProviderFactory: No pending claim expected"
            );
            Staking(stakingAddress).stakeFor(msg.sender, _stakeAmount);
        }

        require (
            serviceProviderEndpointToId[keccak256(bytes(_endpoint))] == 0,
            "ServiceProviderFactory: Endpoint already registered");

        uint256 newServiceProviderID = serviceProviderTypeIDs[_serviceType].add(1);
        serviceProviderTypeIDs[_serviceType] = newServiceProviderID;

        // Index spInfo
        serviceProviderInfo[_serviceType][newServiceProviderID] = ServiceEndpoint({
            owner: msg.sender,
            endpoint: _endpoint,
            blocknumber: block.number,
            delegateOwnerWallet: _delegateOwnerWallet
        });

        // Update endpoint mapping
        serviceProviderEndpointToId[keccak256(bytes(_endpoint))] = newServiceProviderID;

        // Update (address -> type -> ids[])
        serviceProviderAddressToId[msg.sender][_serviceType].push(newServiceProviderID);

        // Increment number of endpoints for this address
        spDetails[msg.sender].numberOfEndpoints = spDetails[msg.sender].numberOfEndpoints.add(1);

        // Update deployer total
        spDetails[msg.sender].deployerStake = (
            spDetails[msg.sender].deployerStake.add(_stakeAmount)
        );

        // Update min and max totals for this service provider
        (, uint256 typeMin, uint256 typeMax) = ServiceTypeManager(
            serviceTypeManagerAddress
        ).getServiceTypeInfo(_serviceType);
        spDetails[msg.sender].minAccountStake = spDetails[msg.sender].minAccountStake.add(typeMin);
        spDetails[msg.sender].maxAccountStake = spDetails[msg.sender].maxAccountStake.add(typeMax);

        // Confirm both aggregate account balance and directly staked amount are valid
        this.validateAccountStakeBalance(msg.sender);
        uint256 currentlyStakedForOwner = Staking(stakingAddress).totalStakedFor(msg.sender);


        // Indicate this service provider is within bounds
        spDetails[msg.sender].validBounds = true;

        emit RegisteredServiceProvider(
            newServiceProviderID,
            _serviceType,
            msg.sender,
            _endpoint,
            currentlyStakedForOwner
        );

        return newServiceProviderID;
    }

    /**
     * @notice Deregister an endpoint from the account of msg.sender
     * @dev Unstakes all tokens for service provider if this is the last endpoint
     * @param _serviceType - type of service to deregister
     * @param _endpoint - endpoint to deregister
     * @return spId of the service that was deregistered
     */
    function deregister(
        bytes32 _serviceType,
        string calldata _endpoint
    ) external returns (uint256)
    {
        _requireIsInitialized();
        _requireStakingAddressIsSet();
        _requireServiceTypeManagerAddressIsSet();

        // Unstake on deregistration if and only if this is the last service endpoint
        uint256 unstakeAmount = 0;
        bool unstaked = false;
        // owned by the service provider
        if (spDetails[msg.sender].numberOfEndpoints == 1) {
            unstakeAmount = spDetails[msg.sender].deployerStake;

            // Submit request to decrease stake, overriding any pending request
            decreaseStakeRequests[msg.sender] = DecreaseStakeRequest({
                decreaseAmount: unstakeAmount,
                lockupExpiryBlock: block.number.add(decreaseStakeLockupDuration)
            });

            unstaked = true;
        }

        require (
            serviceProviderEndpointToId[keccak256(bytes(_endpoint))] != 0,
            "ServiceProviderFactory: Endpoint not registered");

        // Cache invalided service provider ID
        uint256 deregisteredID = serviceProviderEndpointToId[keccak256(bytes(_endpoint))];

        // Update endpoint mapping
        serviceProviderEndpointToId[keccak256(bytes(_endpoint))] = 0;

        require(
            keccak256(bytes(serviceProviderInfo[_serviceType][deregisteredID].endpoint)) == keccak256(bytes(_endpoint)),
            "ServiceProviderFactory: Invalid endpoint for service type");

        require (
            serviceProviderInfo[_serviceType][deregisteredID].owner == msg.sender,
            "ServiceProviderFactory: Only callable by endpoint owner");

        // Update info mapping
        delete serviceProviderInfo[_serviceType][deregisteredID];
        // Reset id, update array
        uint256 spTypeLength = serviceProviderAddressToId[msg.sender][_serviceType].length;
        for (uint256 i = 0; i < spTypeLength; i ++) {
            if (serviceProviderAddressToId[msg.sender][_serviceType][i] == deregisteredID) {
                // Overwrite element to be deleted with last element in array
                serviceProviderAddressToId[msg.sender][_serviceType][i] = serviceProviderAddressToId[msg.sender][_serviceType][spTypeLength - 1];
                // Reduce array size, exit loop
                serviceProviderAddressToId[msg.sender][_serviceType].length--;
                // Confirm this ID has been found for the service provider
                break;
            }
        }

        // Decrement number of endpoints for this address
        spDetails[msg.sender].numberOfEndpoints -= 1;

        // Update min and max totals for this service provider
        (, uint256 typeMin, uint256 typeMax) = ServiceTypeManager(
            serviceTypeManagerAddress
        ).getServiceTypeInfo(_serviceType);
        spDetails[msg.sender].minAccountStake = spDetails[msg.sender].minAccountStake.sub(typeMin);
        spDetails[msg.sender].maxAccountStake = spDetails[msg.sender].maxAccountStake.sub(typeMax);

        emit DeregisteredServiceProvider(
            deregisteredID,
            _serviceType,
            msg.sender,
            _endpoint,
            unstakeAmount);

        // Confirm both aggregate account balance and directly staked amount are valid
        // Only if unstake operation has not occurred
        if (!unstaked) {
            this.validateAccountStakeBalance(msg.sender);
            // Indicate this service provider is within bounds
            spDetails[msg.sender].validBounds = true;
        }

        return deregisteredID;
    }

    /**
     * @notice Increase stake for service provider
     * @param _increaseStakeAmount - amount to increase staked amount by
     * @return New total stake for service provider
     */
    function increaseStake(
        uint256 _increaseStakeAmount
    ) external returns (uint256)
    {
        _requireIsInitialized();
        _requireStakingAddressIsSet();
        _requireClaimsManagerAddressIsSet();

        // Confirm owner has an endpoint
        require(
            spDetails[msg.sender].numberOfEndpoints > 0,
            "ServiceProviderFactory: Registered endpoint required to increase stake"
        );
        require(
            !_claimPending(msg.sender),
            "ServiceProviderFactory: No claim expected to be pending prior to stake transfer"
        );

        Staking stakingContract = Staking(
            stakingAddress
        );

        // Stake increased token amount for msg.sender
        stakingContract.stakeFor(msg.sender, _increaseStakeAmount);

        uint256 newStakeAmount = stakingContract.totalStakedFor(msg.sender);

        // Update deployer total
        spDetails[msg.sender].deployerStake = (
            spDetails[msg.sender].deployerStake.add(_increaseStakeAmount)
        );

        // Confirm both aggregate account balance and directly staked amount are valid
        this.validateAccountStakeBalance(msg.sender);

        // Indicate this service provider is within bounds
        spDetails[msg.sender].validBounds = true;

        emit IncreasedStake(
            msg.sender,
            _increaseStakeAmount,
            newStakeAmount
        );

        return newStakeAmount;
    }

    /**
     * @notice Request to decrease stake. This sets a lockup for decreaseStakeLockupDuration after
               which the actual decreaseStake can be called
     * @dev Decreasing stake is only processed if a service provider is within valid bounds
     * @param _decreaseStakeAmount - amount to decrease stake by in wei
     * @return New total stake amount after the lockup
     */
    function requestDecreaseStake(uint256 _decreaseStakeAmount)
    external returns (uint256)
    {
        _requireIsInitialized();
        _requireStakingAddressIsSet();
        _requireClaimsManagerAddressIsSet();

        require(
            _decreaseStakeAmount > 0,
            "ServiceProviderFactory: Requested stake decrease amount must be greater than zero"
        );
        require(
            !_claimPending(msg.sender),
            "ServiceProviderFactory: No claim expected to be pending prior to stake transfer"
        );

        Staking stakingContract = Staking(
            stakingAddress
        );

        uint256 currentStakeAmount = stakingContract.totalStakedFor(msg.sender);

        // Prohibit decreasing stake to invalid bounds
        _validateBalanceInternal(msg.sender, (currentStakeAmount.sub(_decreaseStakeAmount)));

        uint256 expiryBlock = block.number.add(decreaseStakeLockupDuration);
        decreaseStakeRequests[msg.sender] = DecreaseStakeRequest({
            decreaseAmount: _decreaseStakeAmount,
            lockupExpiryBlock: expiryBlock
        });

        emit DecreaseStakeRequested(msg.sender, _decreaseStakeAmount, expiryBlock);
        return currentStakeAmount.sub(_decreaseStakeAmount);
    }

    /**
     * @notice Cancel a decrease stake request during the lockup
     * @dev Either called by the service provider via DelegateManager or governance
            during a slash action
     * @param _account - address of service provider
     */
    function cancelDecreaseStakeRequest(address _account) external
    {
        _requireIsInitialized();
        _requireDelegateManagerAddressIsSet();

        require(
            msg.sender == _account || msg.sender == delegateManagerAddress,
            "ServiceProviderFactory: Only owner or DelegateManager"
        );
        require(
            _decreaseRequestIsPending(_account),
            "ServiceProviderFactory: Decrease stake request must be pending"
        );

        DecreaseStakeRequest memory cancelledRequest = decreaseStakeRequests[_account];

        // Clear decrease stake request
        decreaseStakeRequests[_account] = DecreaseStakeRequest({
            decreaseAmount: 0,
            lockupExpiryBlock: 0
        });

        emit DecreaseStakeRequestCancelled(
            _account,
            cancelledRequest.decreaseAmount,
            cancelledRequest.lockupExpiryBlock
        );
    }

    /**
     * @notice Called by user to decrease a stake after waiting the appropriate lockup period.
     * @return New total stake after decrease
     */
    function decreaseStake() external returns (uint256)
    {
        _requireIsInitialized();
        _requireStakingAddressIsSet();

        require(
            _decreaseRequestIsPending(msg.sender),
            "ServiceProviderFactory: Decrease stake request must be pending"
        );
        require(
            decreaseStakeRequests[msg.sender].lockupExpiryBlock <= block.number,
            "ServiceProviderFactory: Lockup must be expired"
        );

        Staking stakingContract = Staking(
            stakingAddress
        );

        uint256 decreaseAmount = decreaseStakeRequests[msg.sender].decreaseAmount;
        // Decrease staked token amount for msg.sender
        stakingContract.unstakeFor(msg.sender, decreaseAmount);

        // Query current stake
        uint256 newStakeAmount = stakingContract.totalStakedFor(msg.sender);

        // Update deployer total
        spDetails[msg.sender].deployerStake = (
            spDetails[msg.sender].deployerStake.sub(decreaseAmount)
        );

        // Confirm both aggregate account balance and directly staked amount are valid
        // During registration this validation is bypassed since no endpoints remain
        if (spDetails[msg.sender].numberOfEndpoints > 0) {
            this.validateAccountStakeBalance(msg.sender);
        }

        // Indicate this service provider is within bounds
        spDetails[msg.sender].validBounds = true;

        // Clear decrease stake request
        delete decreaseStakeRequests[msg.sender];

        emit DecreaseStakeRequestEvaluated(msg.sender, decreaseAmount, newStakeAmount);
        return newStakeAmount;
    }

    /**
     * @notice Update delegate owner wallet for a given endpoint
     * @param _serviceType - type of service to register, must be valid in ServiceTypeManager
     * @param _endpoint - url of the service to register - url of the service to register
     * @param _updatedDelegateOwnerWallet - address of new delegate wallet
     */
    function updateDelegateOwnerWallet(
        bytes32 _serviceType,
        string calldata _endpoint,
        address _updatedDelegateOwnerWallet
    ) external
    {
        _requireIsInitialized();

        uint256 spID = this.getServiceProviderIdFromEndpoint(_endpoint);

        require(
            serviceProviderInfo[_serviceType][spID].owner == msg.sender,
            "ServiceProviderFactory: Invalid update operation, wrong owner"
        );

        serviceProviderInfo[_serviceType][spID].delegateOwnerWallet = _updatedDelegateOwnerWallet;
        emit DelegateOwnerWalletUpdated(
            msg.sender,
            _serviceType,
            spID,
            _updatedDelegateOwnerWallet
        );
    }

    /**
     * @notice Update the endpoint for a given service
     * @param _serviceType - type of service to register, must be valid in ServiceTypeManager
     * @param _oldEndpoint - old endpoint currently registered
     * @param _newEndpoint - new endpoint to replace old endpoint
     * @return ID of updated service provider
     */
    function updateEndpoint(
        bytes32 _serviceType,
        string calldata _oldEndpoint,
        string calldata _newEndpoint
    ) external returns (uint256)
    {
        _requireIsInitialized();

        uint256 spId = this.getServiceProviderIdFromEndpoint(_oldEndpoint);
        require (
            spId != 0,
            "ServiceProviderFactory: Could not find service provider with that endpoint"
        );

        ServiceEndpoint memory serviceEndpoint = serviceProviderInfo[_serviceType][spId];

        require(
            serviceEndpoint.owner == msg.sender,
            "ServiceProviderFactory: Invalid update endpoint operation, wrong owner"
        );
        require(
            keccak256(bytes(serviceEndpoint.endpoint)) == keccak256(bytes(_oldEndpoint)),
            "ServiceProviderFactory: Old endpoint doesn't match what's registered for the service provider"
        );

        // invalidate old endpoint
        serviceProviderEndpointToId[keccak256(bytes(serviceEndpoint.endpoint))] = 0;

        // update to new endpoint
        serviceEndpoint.endpoint = _newEndpoint;
        serviceProviderInfo[_serviceType][spId] = serviceEndpoint;
        serviceProviderEndpointToId[keccak256(bytes(_newEndpoint))] = spId;

        emit EndpointUpdated(_serviceType, msg.sender, _oldEndpoint, _newEndpoint, spId);
        return spId;
    }

    /**
     * @notice Update the deployer cut for a given service provider
     * @param _serviceProvider - address of service provider
     * @param _cut - new value for deployer cut
     */
    function requestUpdateDeployerCut(address _serviceProvider, uint256 _cut) external
    {
        _requireIsInitialized();

        require(
            msg.sender == _serviceProvider || msg.sender == governanceAddress,
            ERROR_ONLY_SP_GOVERNANCE
        );

        require(
            (updateDeployerCutRequests[_serviceProvider].lockupExpiryBlock == 0) &&
            (updateDeployerCutRequests[_serviceProvider].newDeployerCut == 0),
            "ServiceProviderFactory: Update deployer cut operation pending"
        );

        require(
            _cut <= DEPLOYER_CUT_BASE,
            "ServiceProviderFactory: Service Provider cut cannot exceed base value"
        );

        uint256 expiryBlock = block.number + deployerCutLockupDuration;
        updateDeployerCutRequests[_serviceProvider] = UpdateDeployerCutRequest({
            lockupExpiryBlock: expiryBlock,
            newDeployerCut: _cut
        });

        emit DeployerCutUpdateRequested(_serviceProvider, _cut, expiryBlock);
    }

    /**
     * @notice Cancel a pending request to update deployer cut
     * @param _serviceProvider - address of service provider
     */
    function cancelUpdateDeployerCut(address _serviceProvider) external
    {
        _requireIsInitialized();
        _requirePendingDeployerCutOperation(_serviceProvider);

        require(
            msg.sender == _serviceProvider || msg.sender == governanceAddress,
            ERROR_ONLY_SP_GOVERNANCE
        );

        UpdateDeployerCutRequest memory cancelledRequest = (
            updateDeployerCutRequests[_serviceProvider]
        );

        // Zero out request information
        delete updateDeployerCutRequests[_serviceProvider];
        emit DeployerCutUpdateRequestCancelled(
            _serviceProvider,
            cancelledRequest.newDeployerCut,
            spDetails[_serviceProvider].deployerCut
        );
    }

    /**
     * @notice Evalue request to update service provider cut of claims
     * @notice Update service provider cut as % of delegate claim, divided by the deployerCutBase.
     * @dev SPs will interact with this value as a percent, value translation done client side
       @dev A value of 5 dictates a 5% cut, with ( 5 / 100 ) * delegateReward going to an SP from each delegator each round.
     */
    function updateDeployerCut(address _serviceProvider) external
    {
        _requireIsInitialized();
        _requirePendingDeployerCutOperation(_serviceProvider);

        require(
            msg.sender == _serviceProvider || msg.sender == governanceAddress,
            ERROR_ONLY_SP_GOVERNANCE
        );

        require(
            updateDeployerCutRequests[_serviceProvider].lockupExpiryBlock <= block.number,
            "ServiceProviderFactory: Lockup must be expired"
        );

        spDetails[_serviceProvider].deployerCut = (
            updateDeployerCutRequests[_serviceProvider].newDeployerCut
        );

        // Zero out request information
        delete updateDeployerCutRequests[_serviceProvider];

        emit DeployerCutUpdateRequestEvaluated(
            _serviceProvider,
            spDetails[_serviceProvider].deployerCut
        );
    }

    /**
     * @notice Update service provider balance
     * @dev Called by DelegateManager by functions modifying entire stake like claim and slash
     * @param _serviceProvider - address of service provider
     * @param _amount - new amount of direct state for service provider
     */
    function updateServiceProviderStake(
        address _serviceProvider,
        uint256 _amount
     ) external
    {
        _requireIsInitialized();
        _requireStakingAddressIsSet();
        _requireDelegateManagerAddressIsSet();

        require(
            msg.sender == delegateManagerAddress,
            "ServiceProviderFactory: only callable by DelegateManager"
        );
        // Update SP tracked total
        spDetails[_serviceProvider].deployerStake = _amount;
        _updateServiceProviderBoundStatus(_serviceProvider);
    }

    /// @notice Update service provider lockup duration
    function updateDecreaseStakeLockupDuration(uint256 _duration) external {
        _requireIsInitialized();

        require(
            msg.sender == governanceAddress,
            ERROR_ONLY_GOVERNANCE
        );

        _updateDecreaseStakeLockupDuration(_duration);
        emit DecreaseStakeLockupDurationUpdated(_duration);
    }

    /// @notice Update service provider lockup duration
    function updateDeployerCutLockupDuration(uint256 _duration) external {
        _requireIsInitialized();

        require(
            msg.sender == governanceAddress,
            ERROR_ONLY_GOVERNANCE
        );

        _updateDeployerCutLockupDuration(_duration);
        emit UpdateDeployerCutLockupDurationUpdated(_duration);
    }

    /// @notice Get denominator for deployer cut calculations
    function getServiceProviderDeployerCutBase()
    external view returns (uint256)
    {
        _requireIsInitialized();

        return DEPLOYER_CUT_BASE;
    }

    /// @notice Get current deployer cut update lockup duration
    function getDeployerCutLockupDuration()
    external view returns (uint256)
    {
        _requireIsInitialized();

        return deployerCutLockupDuration;
    }

    /// @notice Get total number of service providers for a given serviceType
    function getTotalServiceTypeProviders(bytes32 _serviceType)
    external view returns (uint256)
    {
        _requireIsInitialized();

        return serviceProviderTypeIDs[_serviceType];
    }

    /// @notice Get service provider id for an endpoint
    function getServiceProviderIdFromEndpoint(string calldata _endpoint)
    external view returns (uint256)
    {
        _requireIsInitialized();

        return serviceProviderEndpointToId[keccak256(bytes(_endpoint))];
    }

    /**
     * @notice Get service provider ids for a given service provider and service type
     * @return List of service ids of that type for a service provider
     */
    function getServiceProviderIdsFromAddress(address _ownerAddress, bytes32 _serviceType)
    external view returns (uint256[] memory)
    {
        _requireIsInitialized();

        return serviceProviderAddressToId[_ownerAddress][_serviceType];
    }

    /**
     * @notice Get information about a service endpoint given its service id
     * @param _serviceType - type of service, must be a valid service from ServiceTypeManager
     * @param _serviceId - id of service
     */
    function getServiceEndpointInfo(bytes32 _serviceType, uint256 _serviceId)
    external view returns (address owner, string memory endpoint, uint256 blockNumber, address delegateOwnerWallet)
    {
        _requireIsInitialized();

        ServiceEndpoint memory serviceEndpoint = serviceProviderInfo[_serviceType][_serviceId];
        return (
            serviceEndpoint.owner,
            serviceEndpoint.endpoint,
            serviceEndpoint.blocknumber,
            serviceEndpoint.delegateOwnerWallet
        );
    }

    /**
     * @notice Get information about a service provider given their address
     * @param _serviceProvider - address of service provider
     */
    function getServiceProviderDetails(address _serviceProvider)
    external view returns (
        uint256 deployerStake,
        uint256 deployerCut,
        bool validBounds,
        uint256 numberOfEndpoints,
        uint256 minAccountStake,
        uint256 maxAccountStake)
    {
        _requireIsInitialized();

        return (
            spDetails[_serviceProvider].deployerStake,
            spDetails[_serviceProvider].deployerCut,
            spDetails[_serviceProvider].validBounds,
            spDetails[_serviceProvider].numberOfEndpoints,
            spDetails[_serviceProvider].minAccountStake,
            spDetails[_serviceProvider].maxAccountStake
        );
    }

    /**
     * @notice Get information about pending decrease stake requests for service provider
     * @param _serviceProvider - address of service provider
     */
    function getPendingDecreaseStakeRequest(address _serviceProvider)
    external view returns (uint256 amount, uint256 lockupExpiryBlock)
    {
        _requireIsInitialized();

        return (
            decreaseStakeRequests[_serviceProvider].decreaseAmount,
            decreaseStakeRequests[_serviceProvider].lockupExpiryBlock
        );
    }

    /**
     * @notice Get information about pending decrease stake requests for service provider
     * @param _serviceProvider - address of service provider
     */
    function getPendingUpdateDeployerCutRequest(address _serviceProvider)
    external view returns (uint256 newDeployerCut, uint256 lockupExpiryBlock)
    {
        _requireIsInitialized();

        return (
            updateDeployerCutRequests[_serviceProvider].newDeployerCut,
            updateDeployerCutRequests[_serviceProvider].lockupExpiryBlock
        );
    }

    /// @notice Get current unstake lockup duration
    function getDecreaseStakeLockupDuration()
    external view returns (uint256)
    {
        _requireIsInitialized();

        return decreaseStakeLockupDuration;
    }

    /**
     * @notice Validate that the total service provider balance is between the min and max stakes
               for all their registered services and validate  direct stake for sp is above minimum
     * @param _serviceProvider - address of service provider
     */
    function validateAccountStakeBalance(address _serviceProvider)
    external view
    {
        _requireIsInitialized();
        _requireStakingAddressIsSet();

        _validateBalanceInternal(
            _serviceProvider,
            Staking(stakingAddress).totalStakedFor(_serviceProvider)
        );
    }

    /// @notice Get the Governance address
    function getGovernanceAddress() external view returns (address) {
        _requireIsInitialized();

        return governanceAddress;
    }

    /// @notice Get the Staking address
    function getStakingAddress() external view returns (address) {
        _requireIsInitialized();

        return stakingAddress;
    }

    /// @notice Get the DelegateManager address
    function getDelegateManagerAddress() external view returns (address) {
        _requireIsInitialized();

        return delegateManagerAddress;
    }

    /// @notice Get the ServiceTypeManager address
    function getServiceTypeManagerAddress() external view returns (address) {
        _requireIsInitialized();

        return serviceTypeManagerAddress;
    }

    /// @notice Get the ClaimsManager address
    function getClaimsManagerAddress() external view returns (address) {
        _requireIsInitialized();

        return claimsManagerAddress;
    }

    /**
     * @notice Set the Governance address
     * @dev Only callable by Governance address
     * @param _governanceAddress - address for new Governance contract
     */
    function setGovernanceAddress(address _governanceAddress) external {
        _requireIsInitialized();

        require(msg.sender == governanceAddress, ERROR_ONLY_GOVERNANCE);
        _updateGovernanceAddress(_governanceAddress);
        emit GovernanceAddressUpdated(_governanceAddress);
    }

    /**
     * @notice Set the Staking address
     * @dev Only callable by Governance address
     * @param _address - address for new Staking contract
     */
    function setStakingAddress(address _address) external {
        _requireIsInitialized();

        require(msg.sender == governanceAddress, ERROR_ONLY_GOVERNANCE);
        stakingAddress = _address;
        emit StakingAddressUpdated(_address);
    }

    /**
     * @notice Set the DelegateManager address
     * @dev Only callable by Governance address
     * @param _address - address for new DelegateManager contract
     */
    function setDelegateManagerAddress(address _address) external {
        _requireIsInitialized();

        require(msg.sender == governanceAddress, ERROR_ONLY_GOVERNANCE);
        delegateManagerAddress = _address;
        emit DelegateManagerAddressUpdated(_address);
    }

    /**
     * @notice Set the ServiceTypeManager address
     * @dev Only callable by Governance address
     * @param _address - address for new ServiceTypeManager contract
     */
    function setServiceTypeManagerAddress(address _address) external {
        _requireIsInitialized();

        require(msg.sender == governanceAddress, ERROR_ONLY_GOVERNANCE);
        serviceTypeManagerAddress = _address;
        emit ServiceTypeManagerAddressUpdated(_address);
    }

    /**
     * @notice Set the ClaimsManager address
     * @dev Only callable by Governance address
     * @param _address - address for new ClaimsManager contract
     */
    function setClaimsManagerAddress(address _address) external {
        _requireIsInitialized();

        require(msg.sender == governanceAddress, ERROR_ONLY_GOVERNANCE);
        claimsManagerAddress = _address;
        emit ClaimsManagerAddressUpdated(_address);
    }

    // ========================================= Internal Functions =========================================

    /**
     * @notice Update status in spDetails if the bounds for a service provider is valid
     */
    function _updateServiceProviderBoundStatus(address _serviceProvider) internal {
        // Validate bounds for total stake
        uint256 totalSPStake = Staking(stakingAddress).totalStakedFor(_serviceProvider);
        if (totalSPStake < spDetails[_serviceProvider].minAccountStake ||
            totalSPStake > spDetails[_serviceProvider].maxAccountStake) {
            // Indicate this service provider is out of bounds
            spDetails[_serviceProvider].validBounds = false;
        } else {
            // Indicate this service provider is within bounds
            spDetails[_serviceProvider].validBounds = true;
        }
    }

    /**
     * @notice Set the governance address after confirming contract identity
     * @param _governanceAddress - Incoming governance address
     */
    function _updateGovernanceAddress(address _governanceAddress) internal {
        require(
            Governance(_governanceAddress).isGovernanceAddress() == true,
            "ServiceProviderFactory: _governanceAddress is not a valid governance contract"
        );
        governanceAddress = _governanceAddress;
    }

    /**
     * @notice Set the deployer cut lockup duration
     * @param _duration - incoming duration
     */
    function _updateDeployerCutLockupDuration(uint256 _duration) internal
    {
        require(
            ClaimsManager(claimsManagerAddress).getFundingRoundBlockDiff() < _duration,
            "ServiceProviderFactory: Incoming duration must be greater than funding round block diff"
        );
        deployerCutLockupDuration = _duration;
    }

    /**
     * @notice Set the decrease stake lockup duration
     * @param _duration - incoming duration
     */
    function _updateDecreaseStakeLockupDuration(uint256 _duration) internal
    {
        Governance governance = Governance(governanceAddress);
        require(
            _duration > governance.getVotingPeriod() + governance.getExecutionDelay(),
            "ServiceProviderFactory: decreaseStakeLockupDuration duration must be greater than governance votingPeriod + executionDelay"
        );
        decreaseStakeLockupDuration = _duration;
    }

    /**
     * @notice Compare a given amount input against valid min and max bounds for service provider
     * @param _serviceProvider - address of service provider
     * @param _amount - amount in wei to compare
     */
    function _validateBalanceInternal(address _serviceProvider, uint256 _amount) internal view
    {
        require(
            _amount <= spDetails[_serviceProvider].maxAccountStake,
            "ServiceProviderFactory: Maximum stake amount exceeded"
        );
        require(
            spDetails[_serviceProvider].deployerStake >= spDetails[_serviceProvider].minAccountStake,
            "ServiceProviderFactory: Minimum stake requirement not met"
        );
    }

    /**
     * @notice Get whether a decrease request has been initiated for service provider
     * @param _serviceProvider - address of service provider
     * return Boolean of whether decrease request has been initiated
     */
    function _decreaseRequestIsPending(address _serviceProvider)
    internal view returns (bool)
    {
        return (
            (decreaseStakeRequests[_serviceProvider].lockupExpiryBlock > 0) &&
            (decreaseStakeRequests[_serviceProvider].decreaseAmount > 0)
        );
    }

    /**
     * @notice Boolean indicating whether a claim is pending for this service provider
     */
     /**
     * @notice Get whether a claim is pending for this service provider
     * @param _serviceProvider - address of service provider
     * return Boolean of whether claim is pending
     */
    function _claimPending(address _serviceProvider) internal view returns (bool) {
        return ClaimsManager(claimsManagerAddress).claimPending(_serviceProvider);
    }

    // ========================================= Private Functions =========================================
    function _requirePendingDeployerCutOperation (address _serviceProvider) private view {
        require(
            (updateDeployerCutRequests[_serviceProvider].lockupExpiryBlock != 0),
            "ServiceProviderFactory: No update deployer cut operation pending"
        );
    }

    function _requireStakingAddressIsSet() private view {
        require(
            stakingAddress != address(0x00),
            "ServiceProviderFactory: stakingAddress is not set"
        );
    }

    function _requireDelegateManagerAddressIsSet() private view {
        require(
            delegateManagerAddress != address(0x00),
            "ServiceProviderFactory: delegateManagerAddress is not set"
        );
    }

    function _requireServiceTypeManagerAddressIsSet() private view {
        require(
            serviceTypeManagerAddress != address(0x00),
            "ServiceProviderFactory: serviceTypeManagerAddress is not set"
        );
    }

    function _requireClaimsManagerAddressIsSet() private view {
        require(
            claimsManagerAddress != address(0x00),
            "ServiceProviderFactory: claimsManagerAddress is not set"
        );
    }
}

// File: contracts/DelegateManager.sol

pragma solidity ^0.5.0;


/// @notice SafeMath imported via ServiceProviderFactory.sol
/// @notice Governance imported via Staking.sol





/**
 * Designed to manage delegation to staking contract
 */
contract DelegateManager is InitializableV2 {
    using SafeMath for uint256;

    string private constant ERROR_ONLY_GOVERNANCE = (
        "DelegateManager: Only callable by Governance contract"
    );
    string private constant ERROR_MINIMUM_DELEGATION = (
        "DelegateManager: Minimum delegation amount required"
    );
    string private constant ERROR_ONLY_SP_GOVERNANCE = (
        "DelegateManager: Only callable by target SP or governance"
    );
    string private constant ERROR_DELEGATOR_STAKE = (
        "DelegateManager: Delegator must be staked for SP"
    );

    address private governanceAddress;
    address private stakingAddress;
    address private serviceProviderFactoryAddress;
    address private claimsManagerAddress;

    /**
     * Period in  blocks an undelegate operation is delayed.
     * The undelegate operation speed bump is to prevent a delegator from
     *      attempting to remove their delegation in anticipation of a slash.
     * @notice Must be greater than governance votingPeriod + executionDelay
     */
    uint256 private undelegateLockupDuration;

    /// @notice Maximum number of delegators a single account can handle
    uint256 private maxDelegators;

    /// @notice Minimum amount of delegation allowed
    uint256 private minDelegationAmount;

    /**
     * Lockup duration for a remove delegator request.
     * The remove delegator speed bump is to prevent a service provider from maliciously
     *     removing a delegator prior to the evaluation of a proposal.
     * @notice Must be greater than governance votingPeriod + executionDelay
     */
    uint256 private removeDelegatorLockupDuration;

    /**
     * Evaluation period for a remove delegator request
     * @notice added to expiry block calculated for removeDelegatorLockupDuration
     */
    uint256 private removeDelegatorEvalDuration;

    // Staking contract ref
    ERC20Mintable private audiusToken;

    // Struct representing total delegated to SP and list of delegators
    struct ServiceProviderDelegateInfo {
        uint256 totalDelegatedStake;
        uint256 totalLockedUpStake;
        address[] delegators;
    }

    // Data structures for lockup during withdrawal
    struct UndelegateStakeRequest {
        address serviceProvider;
        uint256 amount;
        uint256 lockupExpiryBlock;
    }

    // Service provider address -> ServiceProviderDelegateInfo
    mapping (address => ServiceProviderDelegateInfo) private spDelegateInfo;

    // Delegator stake by address delegated to
    // delegator -> (service provider -> delegatedStake)
    mapping (address => mapping(address => uint256)) private delegateInfo;

    // Delegator stake total by address
    // delegator -> (totalDelegated)
    // Note - delegator properties are maintained in a mapping instead of struct
    // in order to facilitate extensibility in the future.
    mapping (address => uint256) private delegatorTotalStake;

    // Requester to pending undelegate request
    mapping (address => UndelegateStakeRequest) private undelegateRequests;

    // Pending remove delegator requests
    // service provider -> (delegator -> lockupExpiryBlock)
    mapping (address => mapping (address => uint256)) private removeDelegatorRequests;

    event IncreaseDelegatedStake(
        address indexed _delegator,
        address indexed _serviceProvider,
        uint256 indexed _increaseAmount
    );

    event UndelegateStakeRequested(
        address indexed _delegator,
        address indexed _serviceProvider,
        uint256 indexed _amount,
        uint256 _lockupExpiryBlock
    );

    event UndelegateStakeRequestCancelled(
        address indexed _delegator,
        address indexed _serviceProvider,
        uint256 indexed _amount
    );

    event UndelegateStakeRequestEvaluated(
        address indexed _delegator,
        address indexed _serviceProvider,
        uint256 indexed _amount
    );

    event Claim(
        address indexed _claimer,
        uint256 indexed _rewards,
        uint256 indexed _newTotal
    );

    event Slash(
        address indexed _target,
        uint256 indexed _amount,
        uint256 indexed _newTotal
    );

    event RemoveDelegatorRequested(
        address indexed _serviceProvider,
        address indexed _delegator,
        uint256 indexed _lockupExpiryBlock
    );

    event RemoveDelegatorRequestCancelled(
        address indexed _serviceProvider,
        address indexed _delegator
    );

    event RemoveDelegatorRequestEvaluated(
        address indexed _serviceProvider,
        address indexed _delegator,
        uint256 indexed _unstakedAmount
    );

    event MaxDelegatorsUpdated(uint256 indexed _maxDelegators);
    event MinDelegationUpdated(uint256 indexed _minDelegationAmount);
    event UndelegateLockupDurationUpdated(uint256 indexed _undelegateLockupDuration);
    event GovernanceAddressUpdated(address indexed _newGovernanceAddress);
    event StakingAddressUpdated(address indexed _newStakingAddress);
    event ServiceProviderFactoryAddressUpdated(address indexed _newServiceProviderFactoryAddress);
    event ClaimsManagerAddressUpdated(address indexed _newClaimsManagerAddress);
    event RemoveDelegatorLockupDurationUpdated(uint256 indexed _removeDelegatorLockupDuration);
    event RemoveDelegatorEvalDurationUpdated(uint256 indexed _removeDelegatorEvalDuration);

    /**
     * @notice Function to initialize the contract
     * @dev stakingAddress must be initialized separately after Staking contract is deployed
     * @dev serviceProviderFactoryAddress must be initialized separately after ServiceProviderFactory contract is deployed
     * @dev claimsManagerAddress must be initialized separately after ClaimsManager contract is deployed
     * @param _tokenAddress - address of ERC20 token that will be claimed
     * @param _governanceAddress - Governance proxy address
     */
    function initialize (
        address _tokenAddress,
        address _governanceAddress,
        uint256 _undelegateLockupDuration
    ) public initializer
    {
        _updateGovernanceAddress(_governanceAddress);
        audiusToken = ERC20Mintable(_tokenAddress);
        maxDelegators = 175;
        // Default minimum delegation amount set to 100AUD
        minDelegationAmount = 100 * 10**uint256(18);
        InitializableV2.initialize();

        _updateUndelegateLockupDuration(_undelegateLockupDuration);

        // 1 week = 168hrs * 60 min/hr * 60 sec/min / ~13 sec/block = 46523 blocks
        _updateRemoveDelegatorLockupDuration(46523);

        // 24hr * 60min/hr * 60sec/min / ~13 sec/block = 6646 blocks
        removeDelegatorEvalDuration = 6646;
    }

    /**
     * @notice Allow a delegator to delegate stake to a service provider
     * @param _targetSP - address of service provider to delegate to
     * @param _amount - amount in wei to delegate
     * @return Updated total amount delegated to the service provider by delegator
     */
    function delegateStake(
        address _targetSP,
        uint256 _amount
    ) external returns (uint256)
    {
        _requireIsInitialized();
        _requireStakingAddressIsSet();
        _requireServiceProviderFactoryAddressIsSet();
        _requireClaimsManagerAddressIsSet();

        require(
            !_claimPending(_targetSP),
            "DelegateManager: Delegation not permitted for SP pending claim"
        );
        address delegator = msg.sender;
        Staking stakingContract = Staking(stakingAddress);

        // Stake on behalf of target service provider
        stakingContract.delegateStakeFor(
            _targetSP,
            delegator,
            _amount
        );

        // Update list of delegators to SP if necessary
        if (!_delegatorExistsForSP(delegator, _targetSP)) {
            // If not found, update list of delegates
            spDelegateInfo[_targetSP].delegators.push(delegator);
            require(
                spDelegateInfo[_targetSP].delegators.length <= maxDelegators,
                "DelegateManager: Maximum delegators exceeded"
            );
        }

        // Update following values in storage through helper
        // totalServiceProviderDelegatedStake = current sp total + new amount,
        // totalStakedForSpFromDelegator = current delegator total for sp + new amount,
        // totalDelegatorStake = current delegator total + new amount
        _updateDelegatorStake(
            delegator,
            _targetSP,
            spDelegateInfo[_targetSP].totalDelegatedStake.add(_amount),
            delegateInfo[delegator][_targetSP].add(_amount),
            delegatorTotalStake[delegator].add(_amount)
        );

        require(
            delegateInfo[delegator][_targetSP] >= minDelegationAmount,
            ERROR_MINIMUM_DELEGATION
        );

        // Validate balance
        ServiceProviderFactory(
            serviceProviderFactoryAddress
        ).validateAccountStakeBalance(_targetSP);

        emit IncreaseDelegatedStake(
            delegator,
            _targetSP,
            _amount
        );

        // Return new total
        return delegateInfo[delegator][_targetSP];
    }

    /**
     * @notice Submit request for undelegation
     * @param _target - address of service provider to undelegate stake from
     * @param _amount - amount in wei to undelegate
     * @return Updated total amount delegated to the service provider by delegator
     */
    function requestUndelegateStake(
        address _target,
        uint256 _amount
    ) external returns (uint256)
    {
        _requireIsInitialized();
        _requireClaimsManagerAddressIsSet();

        require(
            _amount > 0,
            "DelegateManager: Requested undelegate stake amount must be greater than zero"
        );
        require(
            !_claimPending(_target),
            "DelegateManager: Undelegate request not permitted for SP pending claim"
        );
        address delegator = msg.sender;
        require(
            _delegatorExistsForSP(delegator, _target),
            ERROR_DELEGATOR_STAKE
        );

        // Confirm no pending delegation request
        require(
            !_undelegateRequestIsPending(delegator),
            "DelegateManager: No pending lockup expected"
        );

        // Ensure valid bounds
        uint256 currentlyDelegatedToSP = delegateInfo[delegator][_target];
        require(
            _amount <= currentlyDelegatedToSP,
            "DelegateManager: Cannot decrease greater than currently staked for this ServiceProvider"
        );

        // Submit updated request for sender, with target sp, undelegate amount, target expiry block
        uint256 lockupExpiryBlock = block.number.add(undelegateLockupDuration);
        _updateUndelegateStakeRequest(
            delegator,
            _target,
            _amount,
            lockupExpiryBlock
        );
        // Update total locked for this service provider, increasing by unstake amount
        _updateServiceProviderLockupAmount(
            _target,
            spDelegateInfo[_target].totalLockedUpStake.add(_amount)
        );

        emit UndelegateStakeRequested(delegator, _target, _amount, lockupExpiryBlock);
        return delegateInfo[delegator][_target].sub(_amount);
    }

    /**
     * @notice Cancel undelegation request
     */
    function cancelUndelegateStakeRequest() external {
        _requireIsInitialized();

        address delegator = msg.sender;
        // Confirm pending delegation request
        require(
            _undelegateRequestIsPending(delegator),
            "DelegateManager: Pending lockup expected"
        );
        uint256 unstakeAmount = undelegateRequests[delegator].amount;
        address unlockFundsSP = undelegateRequests[delegator].serviceProvider;
        // Update total locked for this service provider, decreasing by unstake amount
        _updateServiceProviderLockupAmount(
            unlockFundsSP,
            spDelegateInfo[unlockFundsSP].totalLockedUpStake.sub(unstakeAmount)
        );
        // Remove pending request
        _resetUndelegateStakeRequest(delegator);
        emit UndelegateStakeRequestCancelled(delegator, unlockFundsSP, unstakeAmount);
    }

    /**
     * @notice Finalize undelegation request and withdraw stake
     * @return New total amount currently staked after stake has been undelegated
     */
    function undelegateStake() external returns (uint256) {
        _requireIsInitialized();
        _requireStakingAddressIsSet();
        _requireServiceProviderFactoryAddressIsSet();
        _requireClaimsManagerAddressIsSet();

        address delegator = msg.sender;

        // Confirm pending delegation request
        require(
            _undelegateRequestIsPending(delegator),
            "DelegateManager: Pending lockup expected"
        );

        // Confirm lockup expiry has expired
        require(
            undelegateRequests[delegator].lockupExpiryBlock <= block.number,
            "DelegateManager: Lockup must be expired"
        );

        // Confirm no pending claim for this service provider
        require(
            !_claimPending(undelegateRequests[delegator].serviceProvider),
            "DelegateManager: Undelegate not permitted for SP pending claim"
        );

        address serviceProvider = undelegateRequests[delegator].serviceProvider;
        uint256 unstakeAmount = undelegateRequests[delegator].amount;

        // Unstake on behalf of target service provider
        Staking(stakingAddress).undelegateStakeFor(
            serviceProvider,
            delegator,
            unstakeAmount
        );

        // Update total delegated for SP
        // totalServiceProviderDelegatedStake - total amount delegated to service provider
        // totalStakedForSpFromDelegator - amount staked from this delegator to targeted service provider
        _updateDelegatorStake(
            delegator,
            serviceProvider,
            spDelegateInfo[serviceProvider].totalDelegatedStake.sub(unstakeAmount),
            delegateInfo[delegator][serviceProvider].sub(unstakeAmount),
            delegatorTotalStake[delegator].sub(unstakeAmount)
        );

        require(
            (delegateInfo[delegator][serviceProvider] >= minDelegationAmount ||
             delegateInfo[delegator][serviceProvider] == 0),
            ERROR_MINIMUM_DELEGATION
        );

        // Remove from delegators list if no delegated stake remaining
        if (delegateInfo[delegator][serviceProvider] == 0) {
            _removeFromDelegatorsList(serviceProvider, delegator);
        }

        // Update total locked for this service provider, decreasing by unstake amount
        _updateServiceProviderLockupAmount(
            serviceProvider,
            spDelegateInfo[serviceProvider].totalLockedUpStake.sub(unstakeAmount)
        );
        // Reset undelegate request
        _resetUndelegateStakeRequest(delegator);

        emit UndelegateStakeRequestEvaluated(
            delegator,
            serviceProvider,
            unstakeAmount
        );

        // Return new total
        return delegateInfo[delegator][serviceProvider];
    }

    /**
     * @notice Claim and distribute rewards to delegators and service provider as necessary
     * @param _serviceProvider - Provider for which rewards are being distributed
     * @dev Factors in service provider rewards from delegator and transfers deployer cut
     */
    function claimRewards(address _serviceProvider) external {
        _requireIsInitialized();
        _requireStakingAddressIsSet();
        _requireServiceProviderFactoryAddressIsSet();
        _requireClaimsManagerAddressIsSet();

        ServiceProviderFactory spFactory = ServiceProviderFactory(serviceProviderFactoryAddress);

        // Total rewards = (balance in staking) - ((balance in sp factory) + (balance in delegate manager))
        (
            uint256 totalBalanceInStaking,
            uint256 totalBalanceInSPFactory,
            uint256 totalActiveFunds,
            uint256 totalRewards,
            uint256 deployerCut
        ) = _validateClaimRewards(spFactory, _serviceProvider);

        // No-op if balance is already equivalent
        // This case can occur if no rewards due to bound violation or all stake is locked
        if (totalRewards == 0) {
            return;
        }

        uint256 totalDelegatedStakeIncrease = _distributeDelegateRewards(
            _serviceProvider,
            totalActiveFunds,
            totalRewards,
            deployerCut,
            spFactory.getServiceProviderDeployerCutBase()
        );

        // Update total delegated to this SP
        spDelegateInfo[_serviceProvider].totalDelegatedStake = (
            spDelegateInfo[_serviceProvider].totalDelegatedStake.add(totalDelegatedStakeIncrease)
        );

        // spRewardShare represents rewards directly allocated to service provider for their stake
        // Value is computed as the remainder of total minted rewards after distribution to
        // delegators, eliminating any potential for precision loss.
        uint256 spRewardShare = totalRewards.sub(totalDelegatedStakeIncrease);

        // Adding the newly calculated reward share to current balance
        uint256 newSPFactoryBalance = totalBalanceInSPFactory.add(spRewardShare);

        require(
            totalBalanceInStaking == newSPFactoryBalance.add(spDelegateInfo[_serviceProvider].totalDelegatedStake),
            "DelegateManager: claimRewards amount mismatch"
        );

        spFactory.updateServiceProviderStake(
            _serviceProvider,
            newSPFactoryBalance
        );
    }

    /**
     * @notice Reduce current stake amount
     * @dev Only callable by governance. Slashes service provider and delegators equally
     * @param _amount - amount in wei to slash
     * @param _slashAddress - address of service provider to slash
     */
    function slash(uint256 _amount, address _slashAddress)
    external
    {
        _requireIsInitialized();
        _requireStakingAddressIsSet();
        _requireServiceProviderFactoryAddressIsSet();

        require(msg.sender == governanceAddress, ERROR_ONLY_GOVERNANCE);

        Staking stakingContract = Staking(stakingAddress);
        ServiceProviderFactory spFactory = ServiceProviderFactory(serviceProviderFactoryAddress);

        // Amount stored in staking contract for owner
        uint256 totalBalanceInStakingPreSlash = stakingContract.totalStakedFor(_slashAddress);
        require(
            (totalBalanceInStakingPreSlash >= _amount),
            "DelegateManager: Cannot slash more than total currently staked"
        );

        // Cancel any withdrawal request for this service provider
        (uint256 spLockedStake,) = spFactory.getPendingDecreaseStakeRequest(_slashAddress);
        if (spLockedStake > 0) {
            spFactory.cancelDecreaseStakeRequest(_slashAddress);
        }

        // Amount in sp factory for slash target
        (uint256 totalBalanceInSPFactory,,,,,) = (
            spFactory.getServiceProviderDetails(_slashAddress)
        );
        require(
            totalBalanceInSPFactory > 0,
            "DelegateManager: Service Provider stake required"
        );

        // Decrease value in Staking contract
        // A value of zero slash will fail in staking, reverting this transaction
        stakingContract.slash(_amount, _slashAddress);
        uint256 totalBalanceInStakingAfterSlash = stakingContract.totalStakedFor(_slashAddress);

        // Emit slash event
        emit Slash(_slashAddress, _amount, totalBalanceInStakingAfterSlash);

        uint256 totalDelegatedStakeDecrease = 0;
        // For each delegator and deployer, recalculate new value
        // newStakeAmount = newStakeAmount * (oldStakeAmount / totalBalancePreSlash)
        for (uint256 i = 0; i < spDelegateInfo[_slashAddress].delegators.length; i++) {
            address delegator = spDelegateInfo[_slashAddress].delegators[i];
            uint256 preSlashDelegateStake = delegateInfo[delegator][_slashAddress];
            uint256 newDelegateStake = (
             totalBalanceInStakingAfterSlash.mul(preSlashDelegateStake)
            ).div(totalBalanceInStakingPreSlash);
            // slashAmountForDelegator = preSlashDelegateStake - newDelegateStake;
            delegateInfo[delegator][_slashAddress] = (
                delegateInfo[delegator][_slashAddress].sub(preSlashDelegateStake.sub(newDelegateStake))
            );
            // Update total stake for delegator
            _updateDelegatorTotalStake(
                delegator,
                delegatorTotalStake[delegator].sub(preSlashDelegateStake.sub(newDelegateStake))
            );
            // Update total decrease amount
            totalDelegatedStakeDecrease = (
                totalDelegatedStakeDecrease.add(preSlashDelegateStake.sub(newDelegateStake))
            );
            // Check for any locked up funds for this slashed delegator
            // Slash overrides any pending withdrawal requests
            if (undelegateRequests[delegator].amount != 0) {
                address unstakeSP = undelegateRequests[delegator].serviceProvider;
                uint256 unstakeAmount = undelegateRequests[delegator].amount;
                // Remove pending request
                _updateServiceProviderLockupAmount(
                    unstakeSP,
                    spDelegateInfo[unstakeSP].totalLockedUpStake.sub(unstakeAmount)
                );
                _resetUndelegateStakeRequest(delegator);
            }
        }

        // Update total delegated to this SP
        spDelegateInfo[_slashAddress].totalDelegatedStake = (
            spDelegateInfo[_slashAddress].totalDelegatedStake.sub(totalDelegatedStakeDecrease)
        );

        // Remaining decrease applied to service provider
        uint256 totalStakeDecrease = (
            totalBalanceInStakingPreSlash.sub(totalBalanceInStakingAfterSlash)
        );
        uint256 totalSPFactoryBalanceDecrease = (
            totalStakeDecrease.sub(totalDelegatedStakeDecrease)
        );
        spFactory.updateServiceProviderStake(
            _slashAddress,
            totalBalanceInSPFactory.sub(totalSPFactoryBalanceDecrease)
        );
    }

    /**
     * @notice Initiate forcible removal of a delegator
     * @param _serviceProvider - address of service provider
     * @param _delegator - address of delegator
     */
    function requestRemoveDelegator(address _serviceProvider, address _delegator) external {
        _requireIsInitialized();

        require(
            msg.sender == _serviceProvider || msg.sender == governanceAddress,
            ERROR_ONLY_SP_GOVERNANCE
        );

        require(
            removeDelegatorRequests[_serviceProvider][_delegator] == 0,
            "DelegateManager: Pending remove delegator request"
        );

        require(
            _delegatorExistsForSP(_delegator, _serviceProvider),
            ERROR_DELEGATOR_STAKE
        );

        // Update lockup
        removeDelegatorRequests[_serviceProvider][_delegator] = (
            block.number + removeDelegatorLockupDuration
        );

        emit RemoveDelegatorRequested(
            _serviceProvider,
            _delegator,
            removeDelegatorRequests[_serviceProvider][_delegator]
        );
    }

    /**
     * @notice Cancel pending removeDelegator request
     * @param _serviceProvider - address of service provider
     * @param _delegator - address of delegator
     */
    function cancelRemoveDelegatorRequest(address _serviceProvider, address _delegator) external {
        require(
            msg.sender == _serviceProvider || msg.sender == governanceAddress,
            ERROR_ONLY_SP_GOVERNANCE
        );
        require(
            removeDelegatorRequests[_serviceProvider][_delegator] != 0,
            "DelegateManager: No pending request"
        );
        // Reset lockup expiry
        removeDelegatorRequests[_serviceProvider][_delegator] = 0;
        emit RemoveDelegatorRequestCancelled(_serviceProvider, _delegator);
    }

    /**
     * @notice Evaluate removeDelegator request
     * @param _serviceProvider - address of service provider
     * @param _delegator - address of delegator
     * @return Updated total amount delegated to the service provider by delegator
     */
    function removeDelegator(address _serviceProvider, address _delegator) external {
        _requireIsInitialized();
        _requireStakingAddressIsSet();

        require(
            msg.sender == _serviceProvider || msg.sender == governanceAddress,
            ERROR_ONLY_SP_GOVERNANCE
        );

        require(
            removeDelegatorRequests[_serviceProvider][_delegator] != 0,
            "DelegateManager: No pending request"
        );

        // Enforce lockup expiry block
        require(
            block.number >= removeDelegatorRequests[_serviceProvider][_delegator],
            "DelegateManager: Lockup must be expired"
        );

        // Enforce evaluation window for request
        require(
            block.number < removeDelegatorRequests[_serviceProvider][_delegator] + removeDelegatorEvalDuration,
            "DelegateManager: RemoveDelegator evaluation window expired"
        );

        uint256 unstakeAmount = delegateInfo[_delegator][_serviceProvider];
        // Unstake on behalf of target service provider
        Staking(stakingAddress).undelegateStakeFor(
            _serviceProvider,
            _delegator,
            unstakeAmount
        );
        // Update total delegated for SP
        // totalServiceProviderDelegatedStake - total amount delegated to service provider
        // totalStakedForSpFromDelegator - amount staked from this delegator to targeted service provider
        _updateDelegatorStake(
            _delegator,
            _serviceProvider,
            spDelegateInfo[_serviceProvider].totalDelegatedStake.sub(unstakeAmount),
            delegateInfo[_delegator][_serviceProvider].sub(unstakeAmount),
            delegatorTotalStake[_delegator].sub(unstakeAmount)
        );

        if (
            _undelegateRequestIsPending(_delegator) &&
            undelegateRequests[_delegator].serviceProvider == _serviceProvider
        ) {
            // Remove pending request information
            _updateServiceProviderLockupAmount(
                _serviceProvider,
                spDelegateInfo[_serviceProvider].totalLockedUpStake.sub(undelegateRequests[_delegator].amount)
            );
            _resetUndelegateStakeRequest(_delegator);
        }

        // Remove from list of delegators
        _removeFromDelegatorsList(_serviceProvider, _delegator);

        // Reset lockup expiry
        removeDelegatorRequests[_serviceProvider][_delegator] = 0;
        emit RemoveDelegatorRequestEvaluated(_serviceProvider, _delegator, unstakeAmount);
    }

    /**
     * @notice Update duration for undelegate request lockup
     * @param _duration - new lockup duration
     */
    function updateUndelegateLockupDuration(uint256 _duration) external {
        _requireIsInitialized();

        require(msg.sender == governanceAddress, ERROR_ONLY_GOVERNANCE);

        _updateUndelegateLockupDuration(_duration);
        emit UndelegateLockupDurationUpdated(_duration);
    }

    /**
     * @notice Update maximum delegators allowed
     * @param _maxDelegators - new max delegators
     */
    function updateMaxDelegators(uint256 _maxDelegators) external {
        _requireIsInitialized();

        require(msg.sender == governanceAddress, ERROR_ONLY_GOVERNANCE);

        maxDelegators = _maxDelegators;
        emit MaxDelegatorsUpdated(_maxDelegators);
    }

    /**
     * @notice Update minimum delegation amount
     * @param _minDelegationAmount - min new min delegation amount
     */
    function updateMinDelegationAmount(uint256 _minDelegationAmount) external {
        _requireIsInitialized();

        require(msg.sender == governanceAddress, ERROR_ONLY_GOVERNANCE);

        minDelegationAmount = _minDelegationAmount;
        emit MinDelegationUpdated(_minDelegationAmount);
    }

    /**
     * @notice Update remove delegator lockup duration
     * @param _duration - new lockup duration
     */
    function updateRemoveDelegatorLockupDuration(uint256 _duration) external {
        _requireIsInitialized();

        require(msg.sender == governanceAddress, ERROR_ONLY_GOVERNANCE);

        _updateRemoveDelegatorLockupDuration(_duration);
        emit RemoveDelegatorLockupDurationUpdated(_duration);
    }

    /**
     * @notice Update remove delegator evaluation window duration
     * @param _duration - new window duration
     */
    function updateRemoveDelegatorEvalDuration(uint256 _duration) external {
        _requireIsInitialized();

        require(msg.sender == governanceAddress, ERROR_ONLY_GOVERNANCE);

        removeDelegatorEvalDuration = _duration;
        emit RemoveDelegatorEvalDurationUpdated(_duration);
    }

    /**
     * @notice Set the Governance address
     * @dev Only callable by Governance address
     * @param _governanceAddress - address for new Governance contract
     */
    function setGovernanceAddress(address _governanceAddress) external {
        _requireIsInitialized();

        require(msg.sender == governanceAddress, ERROR_ONLY_GOVERNANCE);

        _updateGovernanceAddress(_governanceAddress);
        governanceAddress = _governanceAddress;
        emit GovernanceAddressUpdated(_governanceAddress);
    }

    /**
     * @notice Set the Staking address
     * @dev Only callable by Governance address
     * @param _stakingAddress - address for new Staking contract
     */
    function setStakingAddress(address _stakingAddress) external {
        _requireIsInitialized();

        require(msg.sender == governanceAddress, ERROR_ONLY_GOVERNANCE);
        stakingAddress = _stakingAddress;
        emit StakingAddressUpdated(_stakingAddress);
    }

    /**
     * @notice Set the ServiceProviderFactory address
     * @dev Only callable by Governance address
     * @param _spFactory - address for new ServiceProviderFactory contract
     */
    function setServiceProviderFactoryAddress(address _spFactory) external {
        _requireIsInitialized();

        require(msg.sender == governanceAddress, ERROR_ONLY_GOVERNANCE);
        serviceProviderFactoryAddress = _spFactory;
        emit ServiceProviderFactoryAddressUpdated(_spFactory);
    }

    /**
     * @notice Set the ClaimsManager address
     * @dev Only callable by Governance address
     * @param _claimsManagerAddress - address for new ClaimsManager contract
     */
    function setClaimsManagerAddress(address _claimsManagerAddress) external {
        _requireIsInitialized();

        require(msg.sender == governanceAddress, ERROR_ONLY_GOVERNANCE);
        claimsManagerAddress = _claimsManagerAddress;
        emit ClaimsManagerAddressUpdated(_claimsManagerAddress);
    }

    // ========================================= View Functions =========================================

    /**
     * @notice Get list of delegators for a given service provider
     * @param _sp - service provider address
     */
    function getDelegatorsList(address _sp)
    external view returns (address[] memory)
    {
        _requireIsInitialized();

        return spDelegateInfo[_sp].delegators;
    }

    /**
     * @notice Get total delegation from a given address
     * @param _delegator - delegator address
     */
    function getTotalDelegatorStake(address _delegator)
    external view returns (uint256)
    {
        _requireIsInitialized();

        return delegatorTotalStake[_delegator];
    }

    /// @notice Get total amount delegated to a service provider
    function getTotalDelegatedToServiceProvider(address _sp)
    external view returns (uint256)
    {
        _requireIsInitialized();

        return spDelegateInfo[_sp].totalDelegatedStake;
    }

    /// @notice Get total delegated stake locked up for a service provider
    function getTotalLockedDelegationForServiceProvider(address _sp)
    external view returns (uint256)
    {
        _requireIsInitialized();

        return spDelegateInfo[_sp].totalLockedUpStake;
    }

    /// @notice Get total currently staked for a delegator, for a given service provider
    function getDelegatorStakeForServiceProvider(address _delegator, address _serviceProvider)
    external view returns (uint256)
    {
        _requireIsInitialized();

        return delegateInfo[_delegator][_serviceProvider];
    }

    /**
     * @notice Get status of pending undelegate request for a given address
     * @param _delegator - address of the delegator
     */
    function getPendingUndelegateRequest(address _delegator)
    external view returns (address target, uint256 amount, uint256 lockupExpiryBlock)
    {
        _requireIsInitialized();

        UndelegateStakeRequest memory req = undelegateRequests[_delegator];
        return (req.serviceProvider, req.amount, req.lockupExpiryBlock);
    }

    /**
     * @notice Get status of pending remove delegator request for a given address
     * @param _serviceProvider - address of the service provider
     * @param _delegator - address of the delegator
     * @return - current lockup expiry block for remove delegator request
     */
    function getPendingRemoveDelegatorRequest(
        address _serviceProvider,
        address _delegator
    ) external view returns (uint256)
    {
        _requireIsInitialized();

        return removeDelegatorRequests[_serviceProvider][_delegator];
    }

    /// @notice Get current undelegate lockup duration
    function getUndelegateLockupDuration()
    external view returns (uint256)
    {
        _requireIsInitialized();

        return undelegateLockupDuration;
    }

    /// @notice Current maximum delegators
    function getMaxDelegators()
    external view returns (uint256)
    {
        _requireIsInitialized();

        return maxDelegators;
    }

    /// @notice Get minimum delegation amount
    function getMinDelegationAmount()
    external view returns (uint256)
    {
        _requireIsInitialized();

        return minDelegationAmount;
    }

    /// @notice Get the duration for remove delegator request lockup
    function getRemoveDelegatorLockupDuration()
    external view returns (uint256)
    {
        _requireIsInitialized();

        return removeDelegatorLockupDuration;
    }

    /// @notice Get the duration for evaluation of remove delegator operations
    function getRemoveDelegatorEvalDuration()
    external view returns (uint256)
    {
        _requireIsInitialized();

        return removeDelegatorEvalDuration;
    }

    /// @notice Get the Governance address
    function getGovernanceAddress() external view returns (address) {
        _requireIsInitialized();

        return governanceAddress;
    }

    /// @notice Get the ServiceProviderFactory address
    function getServiceProviderFactoryAddress() external view returns (address) {
        _requireIsInitialized();

        return serviceProviderFactoryAddress;
    }

    /// @notice Get the ClaimsManager address
    function getClaimsManagerAddress() external view returns (address) {
        _requireIsInitialized();

        return claimsManagerAddress;
    }

    /// @notice Get the Staking address
    function getStakingAddress() external view returns (address)
    {
        _requireIsInitialized();

        return stakingAddress;
    }

    // ========================================= Internal functions =========================================

    /**
     * @notice Helper function for claimRewards to get balances from Staking contract
               and do validation
     * @param spFactory - reference to ServiceProviderFactory contract
     * @param _serviceProvider - address for which rewards are being claimed
     * @return (totalBalanceInStaking, totalBalanceInSPFactory, totalActiveFunds, spLockedStake, totalRewards, deployerCut)
     */
    function _validateClaimRewards(ServiceProviderFactory spFactory, address _serviceProvider)
    internal returns (
        uint256 totalBalanceInStaking,
        uint256 totalBalanceInSPFactory,
        uint256 totalActiveFunds,
        uint256 totalRewards,
        uint256 deployerCut
    )
    {
        // Account for any pending locked up stake for the service provider
        (uint256 spLockedStake,) = spFactory.getPendingDecreaseStakeRequest(_serviceProvider);
        uint256 totalLockedUpStake = (
            spDelegateInfo[_serviceProvider].totalLockedUpStake.add(spLockedStake)
        );

        // Process claim for msg.sender
        // Total locked parameter is equal to delegate locked up stake + service provider locked up stake
        uint256 mintedRewards = ClaimsManager(claimsManagerAddress).processClaim(
            _serviceProvider,
            totalLockedUpStake
        );

        // Amount stored in staking contract for owner
        totalBalanceInStaking = Staking(stakingAddress).totalStakedFor(_serviceProvider);

        // Amount in sp factory for claimer
        (
            totalBalanceInSPFactory,
            deployerCut,
            ,,,
        ) = spFactory.getServiceProviderDetails(_serviceProvider);
        // Require active stake to claim any rewards

        // Amount in delegate manager staked to service provider
        uint256 totalBalanceOutsideStaking = (
            totalBalanceInSPFactory.add(spDelegateInfo[_serviceProvider].totalDelegatedStake)
        );

        totalActiveFunds = totalBalanceOutsideStaking.sub(totalLockedUpStake);

        require(
            mintedRewards == totalBalanceInStaking.sub(totalBalanceOutsideStaking),
            "DelegateManager: Reward amount mismatch"
        );

        // Emit claim event
        emit Claim(_serviceProvider, totalRewards, totalBalanceInStaking);

        return (
            totalBalanceInStaking,
            totalBalanceInSPFactory,
            totalActiveFunds,
            mintedRewards,
            deployerCut
        );
    }

    /**
     * @notice Perform state updates when a delegate stake has changed
     * @param _delegator - address of delegator
     * @param _serviceProvider - address of service provider
     * @param _totalServiceProviderDelegatedStake - total delegated to this service provider
     * @param _totalStakedForSpFromDelegator - total delegated to this service provider by delegator
     * @param _totalDelegatorStake - total delegated from this delegator address
     */
    function _updateDelegatorStake(
        address _delegator,
        address _serviceProvider,
        uint256 _totalServiceProviderDelegatedStake,
        uint256 _totalStakedForSpFromDelegator,
        uint256 _totalDelegatorStake
    ) internal
    {
        // Update total delegated for SP
        spDelegateInfo[_serviceProvider].totalDelegatedStake = _totalServiceProviderDelegatedStake;

        // Update amount staked from this delegator to targeted service provider
        delegateInfo[_delegator][_serviceProvider] = _totalStakedForSpFromDelegator;

        // Update total delegated from this delegator
        _updateDelegatorTotalStake(_delegator, _totalDelegatorStake);
    }

    /**
     * @notice Reset pending undelegate stake request
     * @param _delegator - address of delegator
     */
    function _resetUndelegateStakeRequest(address _delegator) internal
    {
        _updateUndelegateStakeRequest(_delegator, address(0), 0, 0);
    }

    /**
     * @notice Perform updates when undelegate request state has changed
     * @param _delegator - address of delegator
     * @param _serviceProvider - address of service provider
     * @param _amount - amount being undelegated
     * @param _lockupExpiryBlock - block at which stake can be undelegated
     */
    function _updateUndelegateStakeRequest(
        address _delegator,
        address _serviceProvider,
        uint256 _amount,
        uint256 _lockupExpiryBlock
    ) internal
    {
        // Update lockup information
        undelegateRequests[_delegator] = UndelegateStakeRequest({
            lockupExpiryBlock: _lockupExpiryBlock,
            amount: _amount,
            serviceProvider: _serviceProvider
        });
    }

    /**
     * @notice Update total amount delegated from an address
     * @param _delegator - address of service provider
     * @param _amount - updated delegator total
     */
    function _updateDelegatorTotalStake(address _delegator, uint256 _amount) internal
    {
        delegatorTotalStake[_delegator] = _amount;
    }

    /**
     * @notice Update amount currently locked up for this service provider
     * @param _serviceProvider - address of service provider
     * @param _updatedLockupAmount - updated lock up amount
     */
    function _updateServiceProviderLockupAmount(
        address _serviceProvider,
        uint256 _updatedLockupAmount
    ) internal
    {
        spDelegateInfo[_serviceProvider].totalLockedUpStake = _updatedLockupAmount;
    }

    function _removeFromDelegatorsList(address _serviceProvider, address _delegator) internal
    {
        for (uint256 i = 0; i < spDelegateInfo[_serviceProvider].delegators.length; i++) {
            if (spDelegateInfo[_serviceProvider].delegators[i] == _delegator) {
                // Overwrite and shrink delegators list
                spDelegateInfo[_serviceProvider].delegators[i] = spDelegateInfo[_serviceProvider].delegators[spDelegateInfo[_serviceProvider].delegators.length - 1];
                spDelegateInfo[_serviceProvider].delegators.length--;
                break;
            }
        }
    }

    /**
     * @notice Helper function to distribute rewards to any delegators
     * @param _sp - service provider account tracked in staking
     * @param _totalActiveFunds - total funds minus any locked stake
     * @param _totalRewards - total rewaards generated in this round
     * @param _deployerCut - service provider cut of delegate rewards, defined as deployerCut / deployerCutBase
     * @param _deployerCutBase - denominator value for calculating service provider cut as a %
     * @return (totalBalanceInStaking, totalBalanceInSPFactory, totalBalanceOutsideStaking)
     */
    function _distributeDelegateRewards(
        address _sp,
        uint256 _totalActiveFunds,
        uint256 _totalRewards,
        uint256 _deployerCut,
        uint256 _deployerCutBase
    )
    internal returns (uint256 totalDelegatedStakeIncrease)
    {
        // Traverse all delegates and calculate their rewards
        // As each delegate reward is calculated, increment SP cut reward accordingly
        for (uint256 i = 0; i < spDelegateInfo[_sp].delegators.length; i++) {
            address delegator = spDelegateInfo[_sp].delegators[i];
            uint256 delegateStakeToSP = delegateInfo[delegator][_sp];

            // Subtract any locked up stake
            if (undelegateRequests[delegator].serviceProvider == _sp) {
                delegateStakeToSP = delegateStakeToSP.sub(undelegateRequests[delegator].amount);
            }

            // Calculate rewards by ((delegateStakeToSP / totalActiveFunds) * totalRewards)
            uint256 rewardsPriorToSPCut = (
              delegateStakeToSP.mul(_totalRewards)
            ).div(_totalActiveFunds);

            // Multiply by deployer cut fraction to calculate reward for SP
            // Operation constructed to perform all multiplication prior to division
            // uint256 spDeployerCut = (rewardsPriorToSPCut * deployerCut ) / (deployerCutBase);
            //                    = ((delegateStakeToSP * totalRewards) / totalActiveFunds) * deployerCut ) / (deployerCutBase);
            //                    = ((delegateStakeToSP * totalRewards * deployerCut) / totalActiveFunds ) / (deployerCutBase);
            //                    = (delegateStakeToSP * totalRewards * deployerCut) / (deployerCutBase * totalActiveFunds);
            uint256 spDeployerCut = (
                (delegateStakeToSP.mul(_totalRewards)).mul(_deployerCut)
            ).div(
                _totalActiveFunds.mul(_deployerCutBase)
            );
            // Increase total delegate reward in DelegateManager
            // Subtract SP reward from rewards to calculate delegate reward
            // delegateReward = rewardsPriorToSPCut - spDeployerCut;
            delegateInfo[delegator][_sp] = (
                delegateInfo[delegator][_sp].add(rewardsPriorToSPCut.sub(spDeployerCut))
            );

            // Update total for this delegator
            _updateDelegatorTotalStake(
                delegator,
                delegatorTotalStake[delegator].add(rewardsPriorToSPCut.sub(spDeployerCut))
            );

            totalDelegatedStakeIncrease = (
                totalDelegatedStakeIncrease.add(rewardsPriorToSPCut.sub(spDeployerCut))
            );
        }

        return (totalDelegatedStakeIncrease);
    }

    /**
     * @notice Set the governance address after confirming contract identity
     * @param _governanceAddress - Incoming governance address
     */
    function _updateGovernanceAddress(address _governanceAddress) internal {
        require(
            Governance(_governanceAddress).isGovernanceAddress() == true,
            "DelegateManager: _governanceAddress is not a valid governance contract"
        );
        governanceAddress = _governanceAddress;
    }

    /**
     * @notice Set the remove delegator lockup duration after validating against governance
     * @param _duration - Incoming remove delegator duration value
     */
    function _updateRemoveDelegatorLockupDuration(uint256 _duration) internal {
        Governance governance = Governance(governanceAddress);
        require(
            _duration > governance.getVotingPeriod() + governance.getExecutionDelay(),
            "DelegateManager: removeDelegatorLockupDuration duration must be greater than governance votingPeriod + executionDelay"
        );
        removeDelegatorLockupDuration = _duration;
    }

    /**
     * @notice Set the undelegate lockup duration after validating against governance
     * @param _duration - Incoming undelegate lockup duration value
     */
    function _updateUndelegateLockupDuration(uint256 _duration) internal {
        Governance governance = Governance(governanceAddress);
        require(
            _duration > governance.getVotingPeriod() + governance.getExecutionDelay(),
            "DelegateManager: undelegateLockupDuration duration must be greater than governance votingPeriod + executionDelay"
        );
        undelegateLockupDuration = _duration;
    }

    /**
     * @notice Returns if delegator has delegated to a service provider
     * @param _delegator - address of delegator
     * @param _serviceProvider - address of service provider
     * @return boolean indicating whether delegator exists for service provider
     */
    function _delegatorExistsForSP(
        address _delegator,
        address _serviceProvider
    ) internal view returns (bool)
    {
        for (uint256 i = 0; i < spDelegateInfo[_serviceProvider].delegators.length; i++) {
            if (spDelegateInfo[_serviceProvider].delegators[i] == _delegator) {
                return true;
            }
        }
        // Not found
        return false;
    }

    /**
     * @notice Determine if a claim is pending for this service provider
     * @param _sp - address of service provider
     * @return boolean indicating whether a claim is pending
     */
    function _claimPending(address _sp) internal view returns (bool) {
        ClaimsManager claimsManager = ClaimsManager(claimsManagerAddress);
        return claimsManager.claimPending(_sp);
    }

    /**
     * @notice Determine if a decrease request has been initiated
     * @param _delegator - address of delegator
     * @return boolean indicating whether a decrease request is pending
     */
    function _undelegateRequestIsPending(address _delegator) internal view returns (bool)
    {
        return (
            (undelegateRequests[_delegator].lockupExpiryBlock != 0) &&
            (undelegateRequests[_delegator].amount != 0) &&
            (undelegateRequests[_delegator].serviceProvider != address(0))
        );
    }

    // ========================================= Private Functions =========================================

    function _requireStakingAddressIsSet() private view {
        require(
            stakingAddress != address(0x00),
            "DelegateManager: stakingAddress is not set"
        );
    }

    function _requireServiceProviderFactoryAddressIsSet() private view {
        require(
            serviceProviderFactoryAddress != address(0x00),
            "DelegateManager: serviceProviderFactoryAddress is not set"
        );
    }

    function _requireClaimsManagerAddressIsSet() private view {
        require(
            claimsManagerAddress != address(0x00),
            "DelegateManager: claimsManagerAddress is not set"
        );
    }
}

Contract Security Audit

Contract ABI

[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_claimer","type":"address"},{"indexed":true,"internalType":"uint256","name":"_rewards","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"_newTotal","type":"uint256"}],"name":"Claim","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_newClaimsManagerAddress","type":"address"}],"name":"ClaimsManagerAddressUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_newGovernanceAddress","type":"address"}],"name":"GovernanceAddressUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_delegator","type":"address"},{"indexed":true,"internalType":"address","name":"_serviceProvider","type":"address"},{"indexed":true,"internalType":"uint256","name":"_increaseAmount","type":"uint256"}],"name":"IncreaseDelegatedStake","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"_maxDelegators","type":"uint256"}],"name":"MaxDelegatorsUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"_minDelegationAmount","type":"uint256"}],"name":"MinDelegationUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"_removeDelegatorEvalDuration","type":"uint256"}],"name":"RemoveDelegatorEvalDurationUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"_removeDelegatorLockupDuration","type":"uint256"}],"name":"RemoveDelegatorLockupDurationUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_serviceProvider","type":"address"},{"indexed":true,"internalType":"address","name":"_delegator","type":"address"}],"name":"RemoveDelegatorRequestCancelled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_serviceProvider","type":"address"},{"indexed":true,"internalType":"address","name":"_delegator","type":"address"},{"indexed":true,"internalType":"uint256","name":"_unstakedAmount","type":"uint256"}],"name":"RemoveDelegatorRequestEvaluated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_serviceProvider","type":"address"},{"indexed":true,"internalType":"address","name":"_delegator","type":"address"},{"indexed":true,"internalType":"uint256","name":"_lockupExpiryBlock","type":"uint256"}],"name":"RemoveDelegatorRequested","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_newServiceProviderFactoryAddress","type":"address"}],"name":"ServiceProviderFactoryAddressUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_target","type":"address"},{"indexed":true,"internalType":"uint256","name":"_amount","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"_newTotal","type":"uint256"}],"name":"Slash","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_newStakingAddress","type":"address"}],"name":"StakingAddressUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"_undelegateLockupDuration","type":"uint256"}],"name":"UndelegateLockupDurationUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_delegator","type":"address"},{"indexed":true,"internalType":"address","name":"_serviceProvider","type":"address"},{"indexed":true,"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"UndelegateStakeRequestCancelled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_delegator","type":"address"},{"indexed":true,"internalType":"address","name":"_serviceProvider","type":"address"},{"indexed":true,"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"UndelegateStakeRequestEvaluated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_delegator","type":"address"},{"indexed":true,"internalType":"address","name":"_serviceProvider","type":"address"},{"indexed":true,"internalType":"uint256","name":"_amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"_lockupExpiryBlock","type":"uint256"}],"name":"UndelegateStakeRequested","type":"event"},{"constant":false,"inputs":[{"internalType":"address","name":"_serviceProvider","type":"address"},{"internalType":"address","name":"_delegator","type":"address"}],"name":"cancelRemoveDelegatorRequest","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"cancelUndelegateStakeRequest","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"_serviceProvider","type":"address"}],"name":"claimRewards","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"_targetSP","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"delegateStake","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getClaimsManagerAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"_delegator","type":"address"},{"internalType":"address","name":"_serviceProvider","type":"address"}],"name":"getDelegatorStakeForServiceProvider","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"_sp","type":"address"}],"name":"getDelegatorsList","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getGovernanceAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getMaxDelegators","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getMinDelegationAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"_serviceProvider","type":"address"},{"internalType":"address","name":"_delegator","type":"address"}],"name":"getPendingRemoveDelegatorRequest","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"_delegator","type":"address"}],"name":"getPendingUndelegateRequest","outputs":[{"internalType":"address","name":"target","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"lockupExpiryBlock","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getRemoveDelegatorEvalDuration","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getRemoveDelegatorLockupDuration","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getServiceProviderFactoryAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getStakingAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"_sp","type":"address"}],"name":"getTotalDelegatedToServiceProvider","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"_delegator","type":"address"}],"name":"getTotalDelegatorStake","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"_sp","type":"address"}],"name":"getTotalLockedDelegationForServiceProvider","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getUndelegateLockupDuration","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"_tokenAddress","type":"address"},{"internalType":"address","name":"_governanceAddress","type":"address"},{"internalType":"uint256","name":"_undelegateLockupDuration","type":"uint256"}],"name":"initialize","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"initialize","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"_serviceProvider","type":"address"},{"internalType":"address","name":"_delegator","type":"address"}],"name":"removeDelegator","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"_serviceProvider","type":"address"},{"internalType":"address","name":"_delegator","type":"address"}],"name":"requestRemoveDelegator","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"_target","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"requestUndelegateStake","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"_claimsManagerAddress","type":"address"}],"name":"setClaimsManagerAddress","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"_governanceAddress","type":"address"}],"name":"setGovernanceAddress","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"_spFactory","type":"address"}],"name":"setServiceProviderFactoryAddress","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"_stakingAddress","type":"address"}],"name":"setStakingAddress","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"address","name":"_slashAddress","type":"address"}],"name":"slash","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"undelegateStake","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"_maxDelegators","type":"uint256"}],"name":"updateMaxDelegators","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"_minDelegationAmount","type":"uint256"}],"name":"updateMinDelegationAmount","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"_duration","type":"uint256"}],"name":"updateRemoveDelegatorEvalDuration","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"_duration","type":"uint256"}],"name":"updateRemoveDelegatorLockupDuration","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"_duration","type":"uint256"}],"name":"updateUndelegateLockupDuration","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}]

608060405234801561001057600080fd5b50614494806100206000396000f3fe608060405234801561001057600080fd5b506004361061021b5760003560e01c8063862c95b911610125578063b9ca6067116100ad578063ef5cfb8c1161007c578063ef5cfb8c146105ee578063f4e0d9ac14610614578063f5c081ad1461063a578063feaf804814610657578063fed3d1fd1461065f5761021b565b8063b9ca60671461054f578063cfc162541461057d578063e0d229ff146105a3578063e37e191c146105d15761021b565b8063a7bac487116100f4578063a7bac487146104b2578063aa70d236146104de578063b0303b7514610504578063b11caba51461052a578063b26df564146105325761021b565b8063862c95b9146104375780639336086f14610454578063948e5426146104a25780639d974fb5146104aa5761021b565b80634a551fe7116101a8578063732524941161017757806373252494146103d35780637dc1eeba146103db5780638129fc1c1461040157806382d51e2c146104095780638504f188146104115761021b565b80634a551fe7146103525780635ad15ada146103805780636a53f10f1461039d578063721e4221146103a55761021b565b80631794bb3c116101ef5780631794bb3c1461026e5780631d0f283a146102a6578063201ae9db146102d45780633c323a1b146102fa5780633d82e3c1146103265761021b565b80622ae74a1461022057806309a945a0146102445780630e9ed68b1461025e57806315fe407014610266575b600080fd5b6102286106d5565b604080516001600160a01b039092168252519081900360200190f35b61024c6106f0565b60408051918252519081900360200190f35b610228610701565b61024c61071b565b6102a46004803603606081101561028457600080fd5b506001600160a01b0381358116916020810135909116906040013561072c565b005b6102a4600480360360408110156102bc57600080fd5b506001600160a01b0381358116916020013516610829565b6102a4600480360360208110156102ea57600080fd5b50356001600160a01b03166109a8565b61024c6004803603604081101561031057600080fd5b506001600160a01b038135169060200135610a8a565b6102a46004803603604081101561033c57600080fd5b50803590602001356001600160a01b0316610dfd565b61024c6004803603604081101561036857600080fd5b506001600160a01b038135811691602001351661159f565b6102a46004803603602081101561039657600080fd5b50356115d5565b6102a46116a0565b6102a4600480360360408110156103bb57600080fd5b506001600160a01b0381358116916020013516611787565b61022861194a565b61024c600480360360208110156103f157600080fd5b50356001600160a01b0316611969565b6102a4611992565b61024c611a41565b61024c6004803603602081101561042757600080fd5b50356001600160a01b0316611a52565b6102a46004803603602081101561044d57600080fd5b5035611a78565b61047a6004803603602081101561046a57600080fd5b50356001600160a01b0316611b43565b604080516001600160a01b039094168452602084019290925282820152519081900360600190f35b610228611ba5565b61024c611bbf565b61024c600480360360408110156104c857600080fd5b506001600160a01b038135169060200135611bd0565b6102a4600480360360208110156104f457600080fd5b50356001600160a01b0316611e64565b61024c6004803603602081101561051a57600080fd5b50356001600160a01b0316611f46565b61024c611f6c565b6102a46004803603602081101561054857600080fd5b5035611f7d565b61024c6004803603604081101561056557600080fd5b506001600160a01b0381358116916020013516612048565b6102a46004803603602081101561059357600080fd5b50356001600160a01b031661207e565b6102a4600480360360408110156105b957600080fd5b506001600160a01b0381358116916020013516612171565b6102a4600480360360208110156105e757600080fd5b5035612541565b6102a46004803603602081101561060457600080fd5b50356001600160a01b0316612610565b6102a46004803603602081101561062a57600080fd5b50356001600160a01b0316612837565b6102a46004803603602081101561065057600080fd5b5035612919565b61024c6129e8565b6106856004803603602081101561067557600080fd5b50356001600160a01b0316612dc2565b60408051602080825283518183015283519192839290830191858101910280838360005b838110156106c15781810151838201526020016106a9565b505050509050019250505060405180910390f35b60006106df612e43565b506035546001600160a01b03165b90565b60006106fa612e43565b5060375490565b600061070b612e43565b506034546001600160a01b031690565b6000610725612e43565b5060385490565b600054610100900460ff16806107455750610745612ece565b80610753575060005460ff16155b61078e5760405162461bcd60e51b815260040180806020018281038252602e815260200180614192602e913960400191505060405180910390fd5b600054610100900460ff161580156107b9576000805460ff1961ff0019909116610100171660011790555b6107c283612ed4565b603c80546001600160a01b0319166001600160a01b03861617905560af60385568056bc75e2d631000006039556107f7611992565b61080082612fa1565b61080b61b5bb6130d0565b6119f6603b558015610823576000805461ff00191690555b50505050565b336001600160a01b038316148061084f575060335461010090046001600160a01b031633145b60405180606001604052806039815260200161405160399139906108f15760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b838110156108b657818101518382015260200161089e565b50505050905090810190601f1680156108e35780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b506001600160a01b038083166000908152604160209081526040808320938516835292905220546109535760405162461bcd60e51b8152600401808060200182810382526023815260200180613f066023913960400191505060405180910390fd5b6001600160a01b03808316600081815260416020908152604080832094861680845294909152808220829055517fd7a1b9c3d30d51412b848777bffec951c371bf58a13788d70c12f534f82d4cb39190a35050565b6109b0612e43565b603360019054906101000a90046001600160a01b03166001600160a01b0316336001600160a01b0316146040518060600160405280603581526020016143206035913990610a3f5760405162461bcd60e51b81526020600482018181528351602484015283519092839260449091019190850190808383600083156108b657818101518382015260200161089e565b50603580546001600160a01b0319166001600160a01b0383169081179091556040517f373f84f0177a6c2e019f2e0e73c988359e56e111629a261c9bba5c968c383ed190600090a250565b6000610a94612e43565b610a9c6131ff565b610aa4613248565b610aac61328f565b610ab5836132d6565b15610af15760405162461bcd60e51b815260040180806020018281038252603e815260200180613ec8603e913960400191505060405180910390fd5b60345460408051636c483ff360e01b81526001600160a01b0386811660048301523360248301819052604483018790529251929316918291636c483ff391606480830192600092919082900301818387803b158015610b4f57600080fd5b505af1158015610b63573d6000803e3d6000fd5b50505050610b71828661335b565b610bff576001600160a01b038581166000818152603d602090815260408220600201805460018101825581845291832090910180546001600160a01b0319169487169490941790935560385491905290541115610bff5760405162461bcd60e51b815260040180806020018281038252602c815260200180613e27602c913960400191505060405180910390fd5b6001600160a01b0385166000908152603d6020526040902054610c949083908790610c30908863ffffffff6133e516565b6001600160a01b038087166000908152603e60209081526040808320938d1683529290522054610c66908963ffffffff6133e516565b6001600160a01b0387166000908152603f6020526040902054610c8f908a63ffffffff6133e516565b613446565b6039546001600160a01b038084166000908152603e60209081526040808320938a16835292815290829020548251606081019093526033808452931115929061408a9083013990610d265760405162461bcd60e51b81526020600482018181528351602484015283519092839260449091019190850190808383600083156108b657818101518382015260200161089e565b50603554604080516303a378e360e61b81526001600160a01b0388811660048301529151919092169163e8de38c0916024808301926000929190829003018186803b158015610d7457600080fd5b505afa158015610d88573d6000803e3d6000fd5b5050505083856001600160a01b0316836001600160a01b03167f82d701855f3ac4a098fc0249261c5e06d1050d23c8aa351fae8abefc2a464fda60405160405180910390a4506001600160a01b039081166000908152603e602090815260408083209387168352929052205490505b92915050565b610e05612e43565b610e0d6131ff565b610e15613248565b603360019054906101000a90046001600160a01b03166001600160a01b0316336001600160a01b0316146040518060600160405280603581526020016143206035913990610ea45760405162461bcd60e51b81526020600482018181528351602484015283519092839260449091019190850190808383600083156108b657818101518382015260200161089e565b5060345460355460408051634b341aed60e01b81526001600160a01b03858116600483015291519382169391909216916000918491634b341aed916024808301926020929190829003018186803b158015610efe57600080fd5b505afa158015610f12573d6000803e3d6000fd5b505050506040513d6020811015610f2857600080fd5b5051905084811015610f6b5760405162461bcd60e51b815260040180806020018281038252603e815260200180614355603e913960400191505060405180910390fd5b604080516001624d61bb60e11b031981526001600160a01b03868116600483015282516000939186169263ff653c8a926024808301939192829003018186803b158015610fb757600080fd5b505afa158015610fcb573d6000803e3d6000fd5b505050506040513d6040811015610fe157600080fd5b50519050801561105c57826001600160a01b03166354350cee866040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b03168152602001915050600060405180830381600087803b15801561104357600080fd5b505af1158015611057573d6000803e3d6000fd5b505050505b6000836001600160a01b031663f273e9a8876040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b0316815260200191505060c06040518083038186803b1580156110b457600080fd5b505afa1580156110c8573d6000803e3d6000fd5b505050506040513d60c08110156110de57600080fd5b505190508061111e5760405162461bcd60e51b81526004018080602001828103825260308152602001806141626030913960400191505060405180910390fd5b846001600160a01b0316633d82e3c188886040518363ffffffff1660e01b815260040180838152602001826001600160a01b03166001600160a01b0316815260200192505050600060405180830381600087803b15801561117e57600080fd5b505af1158015611192573d6000803e3d6000fd5b505050506000856001600160a01b0316634b341aed886040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b0316815260200191505060206040518083038186803b1580156111ee57600080fd5b505afa158015611202573d6000803e3d6000fd5b505050506040513d602081101561121857600080fd5b5051604051909150819089906001600160a01b038a16907fe05ad941535eea602efe44ddd7d96e5db6ad9a4865c360257aad8cf4c0a9446990600090a46000805b6001600160a01b0389166000908152603d602052604090206002015481101561149f576001600160a01b0389166000908152603d602052604081206002018054839081106112a357fe5b60009182526020808320909101546001600160a01b03908116808452603e83526040808520928f16855291909252822054909250906112f8896112ec888563ffffffff61348d16565b9063ffffffff6134e616565b905061136461130d838363ffffffff61352816565b603e6000866001600160a01b03166001600160a01b0316815260200190815260200160002060008f6001600160a01b03166001600160a01b031681526020019081526020016000205461352890919063ffffffff16565b603e6000856001600160a01b03166001600160a01b0316815260200190815260200160002060008e6001600160a01b03166001600160a01b03168152602001908152602001600020819055506113f4836113ef6113ca848661352890919063ffffffff16565b6001600160a01b0387166000908152603f60205260409020549063ffffffff61352816565b61356a565b611414611407838363ffffffff61352816565b869063ffffffff6133e516565b6001600160a01b03841660009081526040602081905290206001015490955015611494576001600160a01b0380841660009081526040602081815281832080546001918201549516808552603d909252919092200154909190611488908390611483908463ffffffff61352816565b613586565b611491856135a5565b50505b505050600101611259565b506001600160a01b0388166000908152603d60205260409020546114c9908263ffffffff61352816565b6001600160a01b0389166000908152603d60205260408120919091556114f5868463ffffffff61352816565b90506000611509828463ffffffff61352816565b90506001600160a01b03881663b90bc8528b61152b888563ffffffff61352816565b6040518363ffffffff1660e01b815260040180836001600160a01b03166001600160a01b0316815260200182815260200192505050600060405180830381600087803b15801561157a57600080fd5b505af115801561158e573d6000803e3d6000fd5b505050505050505050505050505050565b60006115a9612e43565b506001600160a01b03918216600090815260416020908152604080832093909416825291909152205490565b6115dd612e43565b603360019054906101000a90046001600160a01b03166001600160a01b0316336001600160a01b031614604051806060016040528060358152602001614320603591399061166c5760405162461bcd60e51b81526020600482018181528351602484015283519092839260449091019190850190808383600083156108b657818101518382015260200161089e565b50603981905560405181907f2a565983434870f0302d93575c6ee07199767028d6f294c9d1d6a1cd0979f1e190600090a250565b6116a8612e43565b336116b2816135b3565b6116ed5760405162461bcd60e51b81526004018080602001828103825260288152602001806140296028913960400191505060405180910390fd5b6001600160a01b038082166000908152604060208181528183206001808201549154909516808552603d9092529190922090920154611738908290611483908563ffffffff61352816565b611741836135a5565b81816001600160a01b0316846001600160a01b03167fdd2f922d72fb35f887498001c4c6bc61a53f40a51ad38c576e092bc7c688352360405160405180910390a4505050565b61178f612e43565b336001600160a01b03831614806117b5575060335461010090046001600160a01b031633145b604051806060016040528060398152602001614051603991399061181a5760405162461bcd60e51b81526020600482018181528351602484015283519092839260449091019190850190808383600083156108b657818101518382015260200161089e565b506001600160a01b038083166000908152604160209081526040808320938516835292905220541561187d5760405162461bcd60e51b815260040180806020018281038252603181526020018061420c6031913960400191505060405180910390fd5b611887818361335b565b604051806060016040528060308152602001613f2960309139906118ec5760405162461bcd60e51b81526020600482018181528351602484015283519092839260449091019190850190808383600083156108b657818101518382015260200161089e565b50603a546001600160a01b038381166000818152604160209081526040808320948716808452949091528082204390950194859055517fd6f2f5867e98ef295f42626fa37ec5192436d80d6b552dc38c971b9ddbe16e109190a45050565b6000611954612e43565b5060335461010090046001600160a01b031690565b6000611973612e43565b506001600160a01b03166000908152603d602052604090206001015490565b600054610100900460ff16806119ab57506119ab612ece565b806119b9575060005460ff16155b6119f45760405162461bcd60e51b815260040180806020018281038252602e815260200180614192602e913960400191505060405180910390fd5b600054610100900460ff16158015611a1f576000805460ff1961ff0019909116610100171660011790555b6033805460ff191660011790558015611a3e576000805461ff00191690555b50565b6000611a4b612e43565b50603a5490565b6000611a5c612e43565b506001600160a01b03166000908152603d602052604090205490565b611a80612e43565b603360019054906101000a90046001600160a01b03166001600160a01b0316336001600160a01b0316146040518060600160405280603581526020016143206035913990611b0f5760405162461bcd60e51b81526020600482018181528351602484015283519092839260449091019190850190808383600083156108b657818101518382015260200161089e565b50603881905560405181907f6ba19979a519727673bc99b911e17ce26c5b91bbf7471cfc082fea38eb2a488490600090a250565b6000806000611b50612e43565b611b58613dbf565b505050506001600160a01b03908116600090815260406020818152918190208151606081018352815490941680855260018201549385018490526002909101549390910183905292909190565b6000611baf612e43565b506036546001600160a01b031690565b6000611bc9612e43565b50603b5490565b6000611bda612e43565b611be261328f565b60008211611c215760405162461bcd60e51b815260040180806020018281038252604c8152602001806141c0604c913960600191505060405180910390fd5b611c2a836132d6565b15611c665760405162461bcd60e51b8152600401808060200182810382526046815260200180613f836046913960600191505060405180910390fd5b33611c71818561335b565b604051806060016040528060308152602001613f296030913990611cd65760405162461bcd60e51b81526020600482018181528351602484015283519092839260449091019190850190808383600083156108b657818101518382015260200161089e565b50611ce0816135b3565b15611d1c5760405162461bcd60e51b815260040180806020018281038252602b8152602001806142f5602b913960400191505060405180910390fd5b6001600160a01b038082166000908152603e602090815260408083209388168352929052205480841115611d815760405162461bcd60e51b815260040180806020018281038252605781526020018061423d6057913960600191505060405180910390fd5b6000611d98603754436133e590919063ffffffff16565b9050611da68387878461361f565b6001600160a01b0386166000908152603d6020526040902060010154611dd8908790611483908863ffffffff6133e516565b84866001600160a01b0316846001600160a01b03167f0c0ebdfe3f3ccdb3ad070f98a3fb9656a7b8781c299a5c0cd0f37e4d5a02556d846040518082815260200191505060405180910390a46001600160a01b038084166000908152603e60209081526040808320938a1683529290522054611e5a908663ffffffff61352816565b9695505050505050565b611e6c612e43565b603360019054906101000a90046001600160a01b03166001600160a01b0316336001600160a01b0316146040518060600160405280603581526020016143206035913990611efb5760405162461bcd60e51b81526020600482018181528351602484015283519092839260449091019190850190808383600083156108b657818101518382015260200161089e565b50603680546001600160a01b0319166001600160a01b0383169081179091556040517f3b3679838ffd21f454712cf443ab98f11d36d5552da016314c5cbe364a10c24390600090a250565b6000611f50612e43565b506001600160a01b03166000908152603f602052604090205490565b6000611f76612e43565b5060395490565b611f85612e43565b603360019054906101000a90046001600160a01b03166001600160a01b0316336001600160a01b03161460405180606001604052806035815260200161432060359139906120145760405162461bcd60e51b81526020600482018181528351602484015283519092839260449091019190850190808383600083156108b657818101518382015260200161089e565b50603b81905560405181907f10c34e4da809ce0e816d31562e6f5a3d38f913c470dd384ed0a73710281b23dd90600090a250565b6000612052612e43565b506001600160a01b039182166000908152603e6020908152604080832093909416825291909152205490565b612086612e43565b603360019054906101000a90046001600160a01b03166001600160a01b0316336001600160a01b03161460405180606001604052806035815260200161432060359139906121155760405162461bcd60e51b81526020600482018181528351602484015283519092839260449091019190850190808383600083156108b657818101518382015260200161089e565b5061211f81612ed4565b60338054610100600160a81b0319166101006001600160a01b038416908102919091179091556040517fd0e77a42021adb46a85dc0dbcdd75417f2042ed5c51474cb43a25ce0f1049a1e90600090a250565b612179612e43565b6121816131ff565b336001600160a01b03831614806121a7575060335461010090046001600160a01b031633145b604051806060016040528060398152602001614051603991399061220c5760405162461bcd60e51b81526020600482018181528351602484015283519092839260449091019190850190808383600083156108b657818101518382015260200161089e565b506001600160a01b0380831660009081526041602090815260408083209385168352929052205461226e5760405162461bcd60e51b8152600401808060200182810382526023815260200180613f066023913960400191505060405180910390fd5b6001600160a01b038083166000908152604160209081526040808320938516835292905220544310156122d25760405162461bcd60e51b81526004018080602001828103825260278152602001806142ce6027913960400191505060405180910390fd5b603b546001600160a01b038084166000908152604160209081526040808320938616835292905220540143106123395760405162461bcd60e51b815260040180806020018281038252603a815260200180614294603a913960400191505060405180910390fd5b6001600160a01b038082166000818152603e60209081526040808320878616808552925280832054603454825163666cc1c560e11b8152600481019490945260248401959095526044830181905290519094939093169263ccd9838a9260648084019391929182900301818387803b1580156123b457600080fd5b505af11580156123c8573d6000803e3d6000fd5b5050506001600160a01b0384166000908152603d602052604090205461245c9150839085906123fd908563ffffffff61352816565b6001600160a01b038087166000908152603e60209081526040808320938b1683529290522054612433908663ffffffff61352816565b6001600160a01b0387166000908152603f6020526040902054610c8f908763ffffffff61352816565b612465826135b3565b801561248d57506001600160a01b038281166000908152604060208190529020548116908416145b156124de576001600160a01b038083166000908152604060208181528183206001908101549488168452603d909152912001546124d59185916114839163ffffffff61352816565b6124de826135a5565b6124e88383613677565b6001600160a01b0380841660008181526041602090815260408083209487168084529490915280822082905551849392917f912ca4f48e16ea4ec940ef9071c9cc3eb57f01c07e052b1f797caaade6504f8b91a4505050565b612549612e43565b603360019054906101000a90046001600160a01b03166001600160a01b0316336001600160a01b03161460405180606001604052806035815260200161432060359139906125d85760405162461bcd60e51b81526020600482018181528351602484015283519092839260449091019190850190808383600083156108b657818101518382015260200161089e565b506125e281612fa1565b60405181907fcb0491a1854ba445c5afa53dcbe6d6224e52d99cb73840cb58b0c5b79cd434bf90600090a250565b612618612e43565b6126206131ff565b612628613248565b61263061328f565b6035546001600160a01b031660008080808061264c86886137a6565b94509450945094509450816000141561266a57505050505050611a3e565b60006126dd888585858b6001600160a01b0316636c75fdf36040518163ffffffff1660e01b815260040160206040518083038186803b1580156126ac57600080fd5b505afa1580156126c0573d6000803e3d6000fd5b505050506040513d60208110156126d657600080fd5b5051613ab9565b6001600160a01b0389166000908152603d6020526040902054909150612709908263ffffffff6133e516565b6001600160a01b0389166000908152603d6020526040812091909155612735848363ffffffff61352816565b90506000612749878363ffffffff6133e516565b6001600160a01b038b166000908152603d602052604090205490915061277690829063ffffffff6133e516565b88146127b35760405162461bcd60e51b815260040180806020018281038252602d8152602001806143c3602d913960400191505060405180910390fd5b886001600160a01b031663b90bc8528b836040518363ffffffff1660e01b815260040180836001600160a01b03166001600160a01b0316815260200182815260200192505050600060405180830381600087803b15801561281357600080fd5b505af1158015612827573d6000803e3d6000fd5b5050505050505050505050505050565b61283f612e43565b603360019054906101000a90046001600160a01b03166001600160a01b0316336001600160a01b03161460405180606001604052806035815260200161432060359139906128ce5760405162461bcd60e51b81526020600482018181528351602484015283519092839260449091019190850190808383600083156108b657818101518382015260200161089e565b50603480546001600160a01b0319166001600160a01b0383169081179091556040517f8ae96d8af35324a34b19e4f33e72d620b502f69595bb43870ab5fd7a7de7823990600090a250565b612921612e43565b603360019054906101000a90046001600160a01b03166001600160a01b0316336001600160a01b03161460405180606001604052806035815260200161432060359139906129b05760405162461bcd60e51b81526020600482018181528351602484015283519092839260449091019190850190808383600083156108b657818101518382015260200161089e565b506129ba816130d0565b60405181907f6e9686f24e1165005f49d9abb260eb40aed402da21db4894ebd3895a6519a45490600090a250565b60006129f2612e43565b6129fa6131ff565b612a02613248565b612a0a61328f565b33612a14816135b3565b612a4f5760405162461bcd60e51b81526004018080602001828103825260288152602001806140296028913960400191505060405180910390fd5b6001600160a01b038116600090815260406020819052902060020154431015612aa95760405162461bcd60e51b81526004018080602001828103825260278152602001806142ce6027913960400191505060405180910390fd5b6001600160a01b03808216600090815260406020819052902054612acd91166132d6565b15612b095760405162461bcd60e51b815260040180806020018281038252603e8152602001806140de603e913960400191505060405180910390fd5b6001600160a01b038082166000818152604060208190528082208054600190910154603454835163666cc1c560e11b81529287166004840181905260248401969096526044830182905292519495909492169263ccd9838a9260648084019382900301818387803b158015612b7d57600080fd5b505af1158015612b91573d6000803e3d6000fd5b5050506001600160a01b0383166000908152603d6020526040902054612c25915084908490612bc6908563ffffffff61352816565b6001600160a01b038088166000908152603e60209081526040808320938a1683529290522054612bfc908663ffffffff61352816565b6001600160a01b0388166000908152603f6020526040902054610c8f908763ffffffff61352816565b6039546001600160a01b038085166000908152603e6020908152604080832093871683529290522054101580612c7e57506001600160a01b038084166000908152603e6020908152604080832093861683529290522054155b60405180606001604052806033815260200161408a6033913990612ce35760405162461bcd60e51b81526020600482018181528351602484015283519092839260449091019190850190808383600083156108b657818101518382015260200161089e565b506001600160a01b038084166000908152603e6020908152604080832093861683529290522054612d1857612d188284613677565b6001600160a01b0382166000908152603d6020526040902060010154612d4a908390611483908463ffffffff61352816565b612d53836135a5565b80826001600160a01b0316846001600160a01b03167fdf026d8db1c407002e7abde612fb40b6031db7aa35d4b3b699d07627f891e63160405160405180910390a4506001600160a01b039182166000908152603e60209081526040808320939094168252919091522054905090565b6060612dcc612e43565b6001600160a01b0382166000908152603d602090815260409182902060020180548351818402810184019094528084529091830182828015612e3757602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311612e19575b50505050509050919050565b6033546040805180820190915260208082527f496e697469616c697a61626c6556323a204e6f7420696e697469616c697a6564908201529060ff161515600114611a3e5760405162461bcd60e51b81526020600482018181528351602484015283519092839260449091019190850190808383600083156108b657818101518382015260200161089e565b303b1590565b806001600160a01b0316630ea773076040518163ffffffff1660e01b815260040160206040518083038186803b158015612f0d57600080fd5b505afa158015612f21573d6000803e3d6000fd5b505050506040513d6020811015612f3757600080fd5b50511515600114612f795760405162461bcd60e51b815260040180806020018281038252604681526020018061411c6046913960600191505060405180910390fd5b603380546001600160a01b0390921661010002610100600160a81b0319909216919091179055565b6000603360019054906101000a90046001600160a01b03169050806001600160a01b031663062888856040518163ffffffff1660e01b815260040160206040518083038186803b158015612ff457600080fd5b505afa158015613008573d6000803e3d6000fd5b505050506040513d602081101561301e57600080fd5b505160408051633ecc6a4360e01b815290516001600160a01b03841691633ecc6a43916004808301926020929190829003018186803b15801561306057600080fd5b505afa158015613074573d6000803e3d6000fd5b505050506040513d602081101561308a57600080fd5b50510182116130ca5760405162461bcd60e51b81526004018080602001828103825260708152602001806143f06070913960800191505060405180910390fd5b50603755565b6000603360019054906101000a90046001600160a01b03169050806001600160a01b031663062888856040518163ffffffff1660e01b815260040160206040518083038186803b15801561312357600080fd5b505afa158015613137573d6000803e3d6000fd5b505050506040513d602081101561314d57600080fd5b505160408051633ecc6a4360e01b815290516001600160a01b03841691633ecc6a43916004808301926020929190829003018186803b15801561318f57600080fd5b505afa1580156131a3573d6000803e3d6000fd5b505050506040513d60208110156131b957600080fd5b50510182116131f95760405162461bcd60e51b8152600401808060200182810382526075815260200180613e536075913960800191505060405180910390fd5b50603a55565b6034546001600160a01b03166132465760405162461bcd60e51b815260040180806020018281038252602a815260200180613f59602a913960400191505060405180910390fd5b565b6035546001600160a01b03166132465760405162461bcd60e51b8152600401808060200182810382526039815260200180613fc96039913960400191505060405180910390fd5b6036546001600160a01b03166132465760405162461bcd60e51b81526004018080602001828103825260308152602001806143936030913960400191505060405180910390fd5b6036546040805163d017f48360e01b81526001600160a01b03848116600483015291516000939290921691829163d017f483916024808301926020929190829003018186803b15801561332857600080fd5b505afa15801561333c573d6000803e3d6000fd5b505050506040513d602081101561335257600080fd5b50519392505050565b6000805b6001600160a01b0383166000908152603d60205260409020600201548110156133db576001600160a01b038381166000908152603d60205260409020600201805491861691839081106133ae57fe5b6000918252602090912001546001600160a01b031614156133d3576001915050610df7565b60010161335f565b5060009392505050565b60008282018381101561343f576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b9392505050565b6001600160a01b038085166000818152603d602090815260408083208890559389168252603e81528382209282529190915220829055613486858261356a565b5050505050565b60008261349c57506000610df7565b828202828482816134a957fe5b041461343f5760405162461bcd60e51b81526004018080602001828103825260218152602001806140bd6021913960400191505060405180910390fd5b600061343f83836040518060400160405280601a81526020017f536166654d6174683a206469766973696f6e206279207a65726f000000000000815250613d00565b600061343f83836040518060400160405280601e81526020017f536166654d6174683a207375627472616374696f6e206f766572666c6f770000815250613d65565b6001600160a01b039091166000908152603f6020526040902055565b6001600160a01b039091166000908152603d6020526040902060010155565b611a3e81600080600061361f565b6001600160a01b038116600090815260406020819052812060020154158015906135f757506001600160a01b03821660009081526040602081905290206001015415155b8015610df75750506001600160a01b0390811660009081526040602081905290205416151590565b604080516060810182526001600160a01b03948516815260208082019485528183019384529585166000908152958290529420935184546001600160a01b031916931692909217835551600183015551600290910155565b60005b6001600160a01b0383166000908152603d60205260409020600201548110156137a1576001600160a01b038381166000908152603d60205260409020600201805491841691839081106136c957fe5b6000918252602090912001546001600160a01b03161415613799576001600160a01b0383166000908152603d602052604090206002018054600019810190811061370f57fe5b60009182526020808320909101546001600160a01b038681168452603d909252604090922060020180549190921691908390811061374957fe5b600091825260208083209190910180546001600160a01b0319166001600160a01b039485161790559185168152603d90915260409020600201805490613793906000198301613de9565b506137a1565b60010161367a565b505050565b600080600080600080876001600160a01b031663ff653c8a886040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b03168152602001915050604080518083038186803b15801561380457600080fd5b505afa158015613818573d6000803e3d6000fd5b505050506040513d604081101561382e57600080fd5b50516001600160a01b0388166000908152603d602052604081206001015491925090613860908363ffffffff6133e516565b60365460408051631bff085760e21b81526001600160a01b038c811660048301526024820185905291519394506000939190921691636ffc215c91604480830192602092919082900301818787803b1580156138bb57600080fd5b505af11580156138cf573d6000803e3d6000fd5b505050506040513d60208110156138e557600080fd5b505160345460408051634b341aed60e01b81526001600160a01b038d811660048301529151939450911691634b341aed91602480820192602092909190829003018186803b15801561393657600080fd5b505afa15801561394a573d6000803e3d6000fd5b505050506040513d602081101561396057600080fd5b505160408051631e4e7d3560e31b81526001600160a01b038c811660048301529151929a50908c169163f273e9a89160248082019260c092909190829003018186803b1580156139af57600080fd5b505afa1580156139c3573d6000803e3d6000fd5b505050506040513d60c08110156139d957600080fd5b5080516020918201516001600160a01b038c166000908152603d90935260408320549199509550613a1190899063ffffffff6133e516565b9050613a23818463ffffffff61352816565b9650613a35898263ffffffff61352816565b8214613a725760405162461bcd60e51b81526004018080602001828103825260278152602001806140026027913960400191505060405180910390fd5b88868b6001600160a01b03167f34fcbac0073d7c3d388e51312faf357774904998eeb8fca628b9e6f65ee1cbf760405160405180910390a450935050509295509295909350565b6000805b6001600160a01b0387166000908152603d6020526040902060020154811015613cf6576001600160a01b0387166000908152603d60205260408120600201805483908110613b0757fe5b60009182526020808320909101546001600160a01b03908116808452603e835260408085208d84168087529085528186205483875294829052942054909450919291161415613b80576001600160a01b038216600090815260406020819052902060010154613b7d90829063ffffffff61352816565b90505b6000613b96896112ec848b63ffffffff61348d16565b90506000613bcd613bad8b8963ffffffff61348d16565b6112ec8a613bc1878e63ffffffff61348d16565b9063ffffffff61348d16565b9050613c39613be2838363ffffffff61352816565b603e6000876001600160a01b03166001600160a01b0316815260200190815260200160002060008e6001600160a01b03166001600160a01b03168152602001908152602001600020546133e590919063ffffffff16565b603e6000866001600160a01b03166001600160a01b0316815260200190815260200160002060008d6001600160a01b03166001600160a01b0316815260200190815260200160002081905550613cc4846113ef613c9f848661352890919063ffffffff16565b6001600160a01b0388166000908152603f60205260409020549063ffffffff6133e516565b613ce4613cd7838363ffffffff61352816565b879063ffffffff6133e516565b95505060019093019250613abd915050565b5095945050505050565b60008183613d4f5760405162461bcd60e51b81526020600482018181528351602484015283519092839260449091019190850190808383600083156108b657818101518382015260200161089e565b506000838581613d5b57fe5b0495945050505050565b60008184841115613db75760405162461bcd60e51b81526020600482018181528351602484015283519092839260449091019190850190808383600083156108b657818101518382015260200161089e565b505050900390565b604051806060016040528060006001600160a01b0316815260200160008152602001600081525090565b8154818355818111156137a1576000838152602090206137a19181019083016106ed91905b80821115613e225760008155600101613e0e565b509056fe44656c65676174654d616e616765723a204d6178696d756d2064656c656761746f727320657863656564656444656c65676174654d616e616765723a2072656d6f766544656c656761746f724c6f636b75704475726174696f6e206475726174696f6e206d7573742062652067726561746572207468616e20676f7665726e616e636520766f74696e67506572696f64202b20657865637574696f6e44656c617944656c65676174654d616e616765723a2044656c65676174696f6e206e6f74207065726d697474656420666f722053502070656e64696e6720636c61696d44656c65676174654d616e616765723a204e6f2070656e64696e67207265717565737444656c65676174654d616e616765723a2044656c656761746f72206d757374206265207374616b656420666f7220535044656c65676174654d616e616765723a207374616b696e6741646472657373206973206e6f742073657444656c65676174654d616e616765723a20556e64656c65676174652072657175657374206e6f74207065726d697474656420666f722053502070656e64696e6720636c61696d44656c65676174654d616e616765723a207365727669636550726f7669646572466163746f727941646472657373206973206e6f742073657444656c65676174654d616e616765723a2052657761726420616d6f756e74206d69736d6174636844656c65676174654d616e616765723a2050656e64696e67206c6f636b757020657870656374656444656c65676174654d616e616765723a204f6e6c792063616c6c61626c6520627920746172676574205350206f7220676f7665726e616e636544656c65676174654d616e616765723a204d696e696d756d2064656c65676174696f6e20616d6f756e74207265717569726564536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f7744656c65676174654d616e616765723a20556e64656c6567617465206e6f74207065726d697474656420666f722053502070656e64696e6720636c61696d44656c65676174654d616e616765723a205f676f7665726e616e636541646472657373206973206e6f7420612076616c696420676f7665726e616e636520636f6e747261637444656c65676174654d616e616765723a20536572766963652050726f7669646572207374616b65207265717569726564436f6e747261637420696e7374616e63652068617320616c7265616479206265656e20696e697469616c697a656444656c65676174654d616e616765723a2052657175657374656420756e64656c6567617465207374616b6520616d6f756e74206d7573742062652067726561746572207468616e207a65726f44656c65676174654d616e616765723a2050656e64696e672072656d6f76652064656c656761746f72207265717565737444656c65676174654d616e616765723a2043616e6e6f742064656372656173652067726561746572207468616e2063757272656e746c79207374616b656420666f722074686973205365727669636550726f766964657244656c65676174654d616e616765723a2052656d6f766544656c656761746f72206576616c756174696f6e2077696e646f77206578706972656444656c65676174654d616e616765723a204c6f636b7570206d757374206265206578706972656444656c65676174654d616e616765723a204e6f2070656e64696e67206c6f636b757020657870656374656444656c65676174654d616e616765723a204f6e6c792063616c6c61626c6520627920476f7665726e616e636520636f6e747261637444656c65676174654d616e616765723a2043616e6e6f7420736c617368206d6f7265207468616e20746f74616c2063757272656e746c79207374616b656444656c65676174654d616e616765723a20636c61696d734d616e6167657241646472657373206973206e6f742073657444656c65676174654d616e616765723a20636c61696d5265776172647320616d6f756e74206d69736d6174636844656c65676174654d616e616765723a20756e64656c65676174654c6f636b75704475726174696f6e206475726174696f6e206d7573742062652067726561746572207468616e20676f7665726e616e636520766f74696e67506572696f64202b20657865637574696f6e44656c6179a265627a7a72315820adf86fa6a1d687fc958fb7358f740180d41b29edab267cda0b51ec6234ee7bb064736f6c63430005110032

Deployed Bytecode

0x608060405234801561001057600080fd5b506004361061021b5760003560e01c8063862c95b911610125578063b9ca6067116100ad578063ef5cfb8c1161007c578063ef5cfb8c146105ee578063f4e0d9ac14610614578063f5c081ad1461063a578063feaf804814610657578063fed3d1fd1461065f5761021b565b8063b9ca60671461054f578063cfc162541461057d578063e0d229ff146105a3578063e37e191c146105d15761021b565b8063a7bac487116100f4578063a7bac487146104b2578063aa70d236146104de578063b0303b7514610504578063b11caba51461052a578063b26df564146105325761021b565b8063862c95b9146104375780639336086f14610454578063948e5426146104a25780639d974fb5146104aa5761021b565b80634a551fe7116101a8578063732524941161017757806373252494146103d35780637dc1eeba146103db5780638129fc1c1461040157806382d51e2c146104095780638504f188146104115761021b565b80634a551fe7146103525780635ad15ada146103805780636a53f10f1461039d578063721e4221146103a55761021b565b80631794bb3c116101ef5780631794bb3c1461026e5780631d0f283a146102a6578063201ae9db146102d45780633c323a1b146102fa5780633d82e3c1146103265761021b565b80622ae74a1461022057806309a945a0146102445780630e9ed68b1461025e57806315fe407014610266575b600080fd5b6102286106d5565b604080516001600160a01b039092168252519081900360200190f35b61024c6106f0565b60408051918252519081900360200190f35b610228610701565b61024c61071b565b6102a46004803603606081101561028457600080fd5b506001600160a01b0381358116916020810135909116906040013561072c565b005b6102a4600480360360408110156102bc57600080fd5b506001600160a01b0381358116916020013516610829565b6102a4600480360360208110156102ea57600080fd5b50356001600160a01b03166109a8565b61024c6004803603604081101561031057600080fd5b506001600160a01b038135169060200135610a8a565b6102a46004803603604081101561033c57600080fd5b50803590602001356001600160a01b0316610dfd565b61024c6004803603604081101561036857600080fd5b506001600160a01b038135811691602001351661159f565b6102a46004803603602081101561039657600080fd5b50356115d5565b6102a46116a0565b6102a4600480360360408110156103bb57600080fd5b506001600160a01b0381358116916020013516611787565b61022861194a565b61024c600480360360208110156103f157600080fd5b50356001600160a01b0316611969565b6102a4611992565b61024c611a41565b61024c6004803603602081101561042757600080fd5b50356001600160a01b0316611a52565b6102a46004803603602081101561044d57600080fd5b5035611a78565b61047a6004803603602081101561046a57600080fd5b50356001600160a01b0316611b43565b604080516001600160a01b039094168452602084019290925282820152519081900360600190f35b610228611ba5565b61024c611bbf565b61024c600480360360408110156104c857600080fd5b506001600160a01b038135169060200135611bd0565b6102a4600480360360208110156104f457600080fd5b50356001600160a01b0316611e64565b61024c6004803603602081101561051a57600080fd5b50356001600160a01b0316611f46565b61024c611f6c565b6102a46004803603602081101561054857600080fd5b5035611f7d565b61024c6004803603604081101561056557600080fd5b506001600160a01b0381358116916020013516612048565b6102a46004803603602081101561059357600080fd5b50356001600160a01b031661207e565b6102a4600480360360408110156105b957600080fd5b506001600160a01b0381358116916020013516612171565b6102a4600480360360208110156105e757600080fd5b5035612541565b6102a46004803603602081101561060457600080fd5b50356001600160a01b0316612610565b6102a46004803603602081101561062a57600080fd5b50356001600160a01b0316612837565b6102a46004803603602081101561065057600080fd5b5035612919565b61024c6129e8565b6106856004803603602081101561067557600080fd5b50356001600160a01b0316612dc2565b60408051602080825283518183015283519192839290830191858101910280838360005b838110156106c15781810151838201526020016106a9565b505050509050019250505060405180910390f35b60006106df612e43565b506035546001600160a01b03165b90565b60006106fa612e43565b5060375490565b600061070b612e43565b506034546001600160a01b031690565b6000610725612e43565b5060385490565b600054610100900460ff16806107455750610745612ece565b80610753575060005460ff16155b61078e5760405162461bcd60e51b815260040180806020018281038252602e815260200180614192602e913960400191505060405180910390fd5b600054610100900460ff161580156107b9576000805460ff1961ff0019909116610100171660011790555b6107c283612ed4565b603c80546001600160a01b0319166001600160a01b03861617905560af60385568056bc75e2d631000006039556107f7611992565b61080082612fa1565b61080b61b5bb6130d0565b6119f6603b558015610823576000805461ff00191690555b50505050565b336001600160a01b038316148061084f575060335461010090046001600160a01b031633145b60405180606001604052806039815260200161405160399139906108f15760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b838110156108b657818101518382015260200161089e565b50505050905090810190601f1680156108e35780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b506001600160a01b038083166000908152604160209081526040808320938516835292905220546109535760405162461bcd60e51b8152600401808060200182810382526023815260200180613f066023913960400191505060405180910390fd5b6001600160a01b03808316600081815260416020908152604080832094861680845294909152808220829055517fd7a1b9c3d30d51412b848777bffec951c371bf58a13788d70c12f534f82d4cb39190a35050565b6109b0612e43565b603360019054906101000a90046001600160a01b03166001600160a01b0316336001600160a01b0316146040518060600160405280603581526020016143206035913990610a3f5760405162461bcd60e51b81526020600482018181528351602484015283519092839260449091019190850190808383600083156108b657818101518382015260200161089e565b50603580546001600160a01b0319166001600160a01b0383169081179091556040517f373f84f0177a6c2e019f2e0e73c988359e56e111629a261c9bba5c968c383ed190600090a250565b6000610a94612e43565b610a9c6131ff565b610aa4613248565b610aac61328f565b610ab5836132d6565b15610af15760405162461bcd60e51b815260040180806020018281038252603e815260200180613ec8603e913960400191505060405180910390fd5b60345460408051636c483ff360e01b81526001600160a01b0386811660048301523360248301819052604483018790529251929316918291636c483ff391606480830192600092919082900301818387803b158015610b4f57600080fd5b505af1158015610b63573d6000803e3d6000fd5b50505050610b71828661335b565b610bff576001600160a01b038581166000818152603d602090815260408220600201805460018101825581845291832090910180546001600160a01b0319169487169490941790935560385491905290541115610bff5760405162461bcd60e51b815260040180806020018281038252602c815260200180613e27602c913960400191505060405180910390fd5b6001600160a01b0385166000908152603d6020526040902054610c949083908790610c30908863ffffffff6133e516565b6001600160a01b038087166000908152603e60209081526040808320938d1683529290522054610c66908963ffffffff6133e516565b6001600160a01b0387166000908152603f6020526040902054610c8f908a63ffffffff6133e516565b613446565b6039546001600160a01b038084166000908152603e60209081526040808320938a16835292815290829020548251606081019093526033808452931115929061408a9083013990610d265760405162461bcd60e51b81526020600482018181528351602484015283519092839260449091019190850190808383600083156108b657818101518382015260200161089e565b50603554604080516303a378e360e61b81526001600160a01b0388811660048301529151919092169163e8de38c0916024808301926000929190829003018186803b158015610d7457600080fd5b505afa158015610d88573d6000803e3d6000fd5b5050505083856001600160a01b0316836001600160a01b03167f82d701855f3ac4a098fc0249261c5e06d1050d23c8aa351fae8abefc2a464fda60405160405180910390a4506001600160a01b039081166000908152603e602090815260408083209387168352929052205490505b92915050565b610e05612e43565b610e0d6131ff565b610e15613248565b603360019054906101000a90046001600160a01b03166001600160a01b0316336001600160a01b0316146040518060600160405280603581526020016143206035913990610ea45760405162461bcd60e51b81526020600482018181528351602484015283519092839260449091019190850190808383600083156108b657818101518382015260200161089e565b5060345460355460408051634b341aed60e01b81526001600160a01b03858116600483015291519382169391909216916000918491634b341aed916024808301926020929190829003018186803b158015610efe57600080fd5b505afa158015610f12573d6000803e3d6000fd5b505050506040513d6020811015610f2857600080fd5b5051905084811015610f6b5760405162461bcd60e51b815260040180806020018281038252603e815260200180614355603e913960400191505060405180910390fd5b604080516001624d61bb60e11b031981526001600160a01b03868116600483015282516000939186169263ff653c8a926024808301939192829003018186803b158015610fb757600080fd5b505afa158015610fcb573d6000803e3d6000fd5b505050506040513d6040811015610fe157600080fd5b50519050801561105c57826001600160a01b03166354350cee866040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b03168152602001915050600060405180830381600087803b15801561104357600080fd5b505af1158015611057573d6000803e3d6000fd5b505050505b6000836001600160a01b031663f273e9a8876040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b0316815260200191505060c06040518083038186803b1580156110b457600080fd5b505afa1580156110c8573d6000803e3d6000fd5b505050506040513d60c08110156110de57600080fd5b505190508061111e5760405162461bcd60e51b81526004018080602001828103825260308152602001806141626030913960400191505060405180910390fd5b846001600160a01b0316633d82e3c188886040518363ffffffff1660e01b815260040180838152602001826001600160a01b03166001600160a01b0316815260200192505050600060405180830381600087803b15801561117e57600080fd5b505af1158015611192573d6000803e3d6000fd5b505050506000856001600160a01b0316634b341aed886040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b0316815260200191505060206040518083038186803b1580156111ee57600080fd5b505afa158015611202573d6000803e3d6000fd5b505050506040513d602081101561121857600080fd5b5051604051909150819089906001600160a01b038a16907fe05ad941535eea602efe44ddd7d96e5db6ad9a4865c360257aad8cf4c0a9446990600090a46000805b6001600160a01b0389166000908152603d602052604090206002015481101561149f576001600160a01b0389166000908152603d602052604081206002018054839081106112a357fe5b60009182526020808320909101546001600160a01b03908116808452603e83526040808520928f16855291909252822054909250906112f8896112ec888563ffffffff61348d16565b9063ffffffff6134e616565b905061136461130d838363ffffffff61352816565b603e6000866001600160a01b03166001600160a01b0316815260200190815260200160002060008f6001600160a01b03166001600160a01b031681526020019081526020016000205461352890919063ffffffff16565b603e6000856001600160a01b03166001600160a01b0316815260200190815260200160002060008e6001600160a01b03166001600160a01b03168152602001908152602001600020819055506113f4836113ef6113ca848661352890919063ffffffff16565b6001600160a01b0387166000908152603f60205260409020549063ffffffff61352816565b61356a565b611414611407838363ffffffff61352816565b869063ffffffff6133e516565b6001600160a01b03841660009081526040602081905290206001015490955015611494576001600160a01b0380841660009081526040602081815281832080546001918201549516808552603d909252919092200154909190611488908390611483908463ffffffff61352816565b613586565b611491856135a5565b50505b505050600101611259565b506001600160a01b0388166000908152603d60205260409020546114c9908263ffffffff61352816565b6001600160a01b0389166000908152603d60205260408120919091556114f5868463ffffffff61352816565b90506000611509828463ffffffff61352816565b90506001600160a01b03881663b90bc8528b61152b888563ffffffff61352816565b6040518363ffffffff1660e01b815260040180836001600160a01b03166001600160a01b0316815260200182815260200192505050600060405180830381600087803b15801561157a57600080fd5b505af115801561158e573d6000803e3d6000fd5b505050505050505050505050505050565b60006115a9612e43565b506001600160a01b03918216600090815260416020908152604080832093909416825291909152205490565b6115dd612e43565b603360019054906101000a90046001600160a01b03166001600160a01b0316336001600160a01b031614604051806060016040528060358152602001614320603591399061166c5760405162461bcd60e51b81526020600482018181528351602484015283519092839260449091019190850190808383600083156108b657818101518382015260200161089e565b50603981905560405181907f2a565983434870f0302d93575c6ee07199767028d6f294c9d1d6a1cd0979f1e190600090a250565b6116a8612e43565b336116b2816135b3565b6116ed5760405162461bcd60e51b81526004018080602001828103825260288152602001806140296028913960400191505060405180910390fd5b6001600160a01b038082166000908152604060208181528183206001808201549154909516808552603d9092529190922090920154611738908290611483908563ffffffff61352816565b611741836135a5565b81816001600160a01b0316846001600160a01b03167fdd2f922d72fb35f887498001c4c6bc61a53f40a51ad38c576e092bc7c688352360405160405180910390a4505050565b61178f612e43565b336001600160a01b03831614806117b5575060335461010090046001600160a01b031633145b604051806060016040528060398152602001614051603991399061181a5760405162461bcd60e51b81526020600482018181528351602484015283519092839260449091019190850190808383600083156108b657818101518382015260200161089e565b506001600160a01b038083166000908152604160209081526040808320938516835292905220541561187d5760405162461bcd60e51b815260040180806020018281038252603181526020018061420c6031913960400191505060405180910390fd5b611887818361335b565b604051806060016040528060308152602001613f2960309139906118ec5760405162461bcd60e51b81526020600482018181528351602484015283519092839260449091019190850190808383600083156108b657818101518382015260200161089e565b50603a546001600160a01b038381166000818152604160209081526040808320948716808452949091528082204390950194859055517fd6f2f5867e98ef295f42626fa37ec5192436d80d6b552dc38c971b9ddbe16e109190a45050565b6000611954612e43565b5060335461010090046001600160a01b031690565b6000611973612e43565b506001600160a01b03166000908152603d602052604090206001015490565b600054610100900460ff16806119ab57506119ab612ece565b806119b9575060005460ff16155b6119f45760405162461bcd60e51b815260040180806020018281038252602e815260200180614192602e913960400191505060405180910390fd5b600054610100900460ff16158015611a1f576000805460ff1961ff0019909116610100171660011790555b6033805460ff191660011790558015611a3e576000805461ff00191690555b50565b6000611a4b612e43565b50603a5490565b6000611a5c612e43565b506001600160a01b03166000908152603d602052604090205490565b611a80612e43565b603360019054906101000a90046001600160a01b03166001600160a01b0316336001600160a01b0316146040518060600160405280603581526020016143206035913990611b0f5760405162461bcd60e51b81526020600482018181528351602484015283519092839260449091019190850190808383600083156108b657818101518382015260200161089e565b50603881905560405181907f6ba19979a519727673bc99b911e17ce26c5b91bbf7471cfc082fea38eb2a488490600090a250565b6000806000611b50612e43565b611b58613dbf565b505050506001600160a01b03908116600090815260406020818152918190208151606081018352815490941680855260018201549385018490526002909101549390910183905292909190565b6000611baf612e43565b506036546001600160a01b031690565b6000611bc9612e43565b50603b5490565b6000611bda612e43565b611be261328f565b60008211611c215760405162461bcd60e51b815260040180806020018281038252604c8152602001806141c0604c913960600191505060405180910390fd5b611c2a836132d6565b15611c665760405162461bcd60e51b8152600401808060200182810382526046815260200180613f836046913960600191505060405180910390fd5b33611c71818561335b565b604051806060016040528060308152602001613f296030913990611cd65760405162461bcd60e51b81526020600482018181528351602484015283519092839260449091019190850190808383600083156108b657818101518382015260200161089e565b50611ce0816135b3565b15611d1c5760405162461bcd60e51b815260040180806020018281038252602b8152602001806142f5602b913960400191505060405180910390fd5b6001600160a01b038082166000908152603e602090815260408083209388168352929052205480841115611d815760405162461bcd60e51b815260040180806020018281038252605781526020018061423d6057913960600191505060405180910390fd5b6000611d98603754436133e590919063ffffffff16565b9050611da68387878461361f565b6001600160a01b0386166000908152603d6020526040902060010154611dd8908790611483908863ffffffff6133e516565b84866001600160a01b0316846001600160a01b03167f0c0ebdfe3f3ccdb3ad070f98a3fb9656a7b8781c299a5c0cd0f37e4d5a02556d846040518082815260200191505060405180910390a46001600160a01b038084166000908152603e60209081526040808320938a1683529290522054611e5a908663ffffffff61352816565b9695505050505050565b611e6c612e43565b603360019054906101000a90046001600160a01b03166001600160a01b0316336001600160a01b0316146040518060600160405280603581526020016143206035913990611efb5760405162461bcd60e51b81526020600482018181528351602484015283519092839260449091019190850190808383600083156108b657818101518382015260200161089e565b50603680546001600160a01b0319166001600160a01b0383169081179091556040517f3b3679838ffd21f454712cf443ab98f11d36d5552da016314c5cbe364a10c24390600090a250565b6000611f50612e43565b506001600160a01b03166000908152603f602052604090205490565b6000611f76612e43565b5060395490565b611f85612e43565b603360019054906101000a90046001600160a01b03166001600160a01b0316336001600160a01b03161460405180606001604052806035815260200161432060359139906120145760405162461bcd60e51b81526020600482018181528351602484015283519092839260449091019190850190808383600083156108b657818101518382015260200161089e565b50603b81905560405181907f10c34e4da809ce0e816d31562e6f5a3d38f913c470dd384ed0a73710281b23dd90600090a250565b6000612052612e43565b506001600160a01b039182166000908152603e6020908152604080832093909416825291909152205490565b612086612e43565b603360019054906101000a90046001600160a01b03166001600160a01b0316336001600160a01b03161460405180606001604052806035815260200161432060359139906121155760405162461bcd60e51b81526020600482018181528351602484015283519092839260449091019190850190808383600083156108b657818101518382015260200161089e565b5061211f81612ed4565b60338054610100600160a81b0319166101006001600160a01b038416908102919091179091556040517fd0e77a42021adb46a85dc0dbcdd75417f2042ed5c51474cb43a25ce0f1049a1e90600090a250565b612179612e43565b6121816131ff565b336001600160a01b03831614806121a7575060335461010090046001600160a01b031633145b604051806060016040528060398152602001614051603991399061220c5760405162461bcd60e51b81526020600482018181528351602484015283519092839260449091019190850190808383600083156108b657818101518382015260200161089e565b506001600160a01b0380831660009081526041602090815260408083209385168352929052205461226e5760405162461bcd60e51b8152600401808060200182810382526023815260200180613f066023913960400191505060405180910390fd5b6001600160a01b038083166000908152604160209081526040808320938516835292905220544310156122d25760405162461bcd60e51b81526004018080602001828103825260278152602001806142ce6027913960400191505060405180910390fd5b603b546001600160a01b038084166000908152604160209081526040808320938616835292905220540143106123395760405162461bcd60e51b815260040180806020018281038252603a815260200180614294603a913960400191505060405180910390fd5b6001600160a01b038082166000818152603e60209081526040808320878616808552925280832054603454825163666cc1c560e11b8152600481019490945260248401959095526044830181905290519094939093169263ccd9838a9260648084019391929182900301818387803b1580156123b457600080fd5b505af11580156123c8573d6000803e3d6000fd5b5050506001600160a01b0384166000908152603d602052604090205461245c9150839085906123fd908563ffffffff61352816565b6001600160a01b038087166000908152603e60209081526040808320938b1683529290522054612433908663ffffffff61352816565b6001600160a01b0387166000908152603f6020526040902054610c8f908763ffffffff61352816565b612465826135b3565b801561248d57506001600160a01b038281166000908152604060208190529020548116908416145b156124de576001600160a01b038083166000908152604060208181528183206001908101549488168452603d909152912001546124d59185916114839163ffffffff61352816565b6124de826135a5565b6124e88383613677565b6001600160a01b0380841660008181526041602090815260408083209487168084529490915280822082905551849392917f912ca4f48e16ea4ec940ef9071c9cc3eb57f01c07e052b1f797caaade6504f8b91a4505050565b612549612e43565b603360019054906101000a90046001600160a01b03166001600160a01b0316336001600160a01b03161460405180606001604052806035815260200161432060359139906125d85760405162461bcd60e51b81526020600482018181528351602484015283519092839260449091019190850190808383600083156108b657818101518382015260200161089e565b506125e281612fa1565b60405181907fcb0491a1854ba445c5afa53dcbe6d6224e52d99cb73840cb58b0c5b79cd434bf90600090a250565b612618612e43565b6126206131ff565b612628613248565b61263061328f565b6035546001600160a01b031660008080808061264c86886137a6565b94509450945094509450816000141561266a57505050505050611a3e565b60006126dd888585858b6001600160a01b0316636c75fdf36040518163ffffffff1660e01b815260040160206040518083038186803b1580156126ac57600080fd5b505afa1580156126c0573d6000803e3d6000fd5b505050506040513d60208110156126d657600080fd5b5051613ab9565b6001600160a01b0389166000908152603d6020526040902054909150612709908263ffffffff6133e516565b6001600160a01b0389166000908152603d6020526040812091909155612735848363ffffffff61352816565b90506000612749878363ffffffff6133e516565b6001600160a01b038b166000908152603d602052604090205490915061277690829063ffffffff6133e516565b88146127b35760405162461bcd60e51b815260040180806020018281038252602d8152602001806143c3602d913960400191505060405180910390fd5b886001600160a01b031663b90bc8528b836040518363ffffffff1660e01b815260040180836001600160a01b03166001600160a01b0316815260200182815260200192505050600060405180830381600087803b15801561281357600080fd5b505af1158015612827573d6000803e3d6000fd5b5050505050505050505050505050565b61283f612e43565b603360019054906101000a90046001600160a01b03166001600160a01b0316336001600160a01b03161460405180606001604052806035815260200161432060359139906128ce5760405162461bcd60e51b81526020600482018181528351602484015283519092839260449091019190850190808383600083156108b657818101518382015260200161089e565b50603480546001600160a01b0319166001600160a01b0383169081179091556040517f8ae96d8af35324a34b19e4f33e72d620b502f69595bb43870ab5fd7a7de7823990600090a250565b612921612e43565b603360019054906101000a90046001600160a01b03166001600160a01b0316336001600160a01b03161460405180606001604052806035815260200161432060359139906129b05760405162461bcd60e51b81526020600482018181528351602484015283519092839260449091019190850190808383600083156108b657818101518382015260200161089e565b506129ba816130d0565b60405181907f6e9686f24e1165005f49d9abb260eb40aed402da21db4894ebd3895a6519a45490600090a250565b60006129f2612e43565b6129fa6131ff565b612a02613248565b612a0a61328f565b33612a14816135b3565b612a4f5760405162461bcd60e51b81526004018080602001828103825260288152602001806140296028913960400191505060405180910390fd5b6001600160a01b038116600090815260406020819052902060020154431015612aa95760405162461bcd60e51b81526004018080602001828103825260278152602001806142ce6027913960400191505060405180910390fd5b6001600160a01b03808216600090815260406020819052902054612acd91166132d6565b15612b095760405162461bcd60e51b815260040180806020018281038252603e8152602001806140de603e913960400191505060405180910390fd5b6001600160a01b038082166000818152604060208190528082208054600190910154603454835163666cc1c560e11b81529287166004840181905260248401969096526044830182905292519495909492169263ccd9838a9260648084019382900301818387803b158015612b7d57600080fd5b505af1158015612b91573d6000803e3d6000fd5b5050506001600160a01b0383166000908152603d6020526040902054612c25915084908490612bc6908563ffffffff61352816565b6001600160a01b038088166000908152603e60209081526040808320938a1683529290522054612bfc908663ffffffff61352816565b6001600160a01b0388166000908152603f6020526040902054610c8f908763ffffffff61352816565b6039546001600160a01b038085166000908152603e6020908152604080832093871683529290522054101580612c7e57506001600160a01b038084166000908152603e6020908152604080832093861683529290522054155b60405180606001604052806033815260200161408a6033913990612ce35760405162461bcd60e51b81526020600482018181528351602484015283519092839260449091019190850190808383600083156108b657818101518382015260200161089e565b506001600160a01b038084166000908152603e6020908152604080832093861683529290522054612d1857612d188284613677565b6001600160a01b0382166000908152603d6020526040902060010154612d4a908390611483908463ffffffff61352816565b612d53836135a5565b80826001600160a01b0316846001600160a01b03167fdf026d8db1c407002e7abde612fb40b6031db7aa35d4b3b699d07627f891e63160405160405180910390a4506001600160a01b039182166000908152603e60209081526040808320939094168252919091522054905090565b6060612dcc612e43565b6001600160a01b0382166000908152603d602090815260409182902060020180548351818402810184019094528084529091830182828015612e3757602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311612e19575b50505050509050919050565b6033546040805180820190915260208082527f496e697469616c697a61626c6556323a204e6f7420696e697469616c697a6564908201529060ff161515600114611a3e5760405162461bcd60e51b81526020600482018181528351602484015283519092839260449091019190850190808383600083156108b657818101518382015260200161089e565b303b1590565b806001600160a01b0316630ea773076040518163ffffffff1660e01b815260040160206040518083038186803b158015612f0d57600080fd5b505afa158015612f21573d6000803e3d6000fd5b505050506040513d6020811015612f3757600080fd5b50511515600114612f795760405162461bcd60e51b815260040180806020018281038252604681526020018061411c6046913960600191505060405180910390fd5b603380546001600160a01b0390921661010002610100600160a81b0319909216919091179055565b6000603360019054906101000a90046001600160a01b03169050806001600160a01b031663062888856040518163ffffffff1660e01b815260040160206040518083038186803b158015612ff457600080fd5b505afa158015613008573d6000803e3d6000fd5b505050506040513d602081101561301e57600080fd5b505160408051633ecc6a4360e01b815290516001600160a01b03841691633ecc6a43916004808301926020929190829003018186803b15801561306057600080fd5b505afa158015613074573d6000803e3d6000fd5b505050506040513d602081101561308a57600080fd5b50510182116130ca5760405162461bcd60e51b81526004018080602001828103825260708152602001806143f06070913960800191505060405180910390fd5b50603755565b6000603360019054906101000a90046001600160a01b03169050806001600160a01b031663062888856040518163ffffffff1660e01b815260040160206040518083038186803b15801561312357600080fd5b505afa158015613137573d6000803e3d6000fd5b505050506040513d602081101561314d57600080fd5b505160408051633ecc6a4360e01b815290516001600160a01b03841691633ecc6a43916004808301926020929190829003018186803b15801561318f57600080fd5b505afa1580156131a3573d6000803e3d6000fd5b505050506040513d60208110156131b957600080fd5b50510182116131f95760405162461bcd60e51b8152600401808060200182810382526075815260200180613e536075913960800191505060405180910390fd5b50603a55565b6034546001600160a01b03166132465760405162461bcd60e51b815260040180806020018281038252602a815260200180613f59602a913960400191505060405180910390fd5b565b6035546001600160a01b03166132465760405162461bcd60e51b8152600401808060200182810382526039815260200180613fc96039913960400191505060405180910390fd5b6036546001600160a01b03166132465760405162461bcd60e51b81526004018080602001828103825260308152602001806143936030913960400191505060405180910390fd5b6036546040805163d017f48360e01b81526001600160a01b03848116600483015291516000939290921691829163d017f483916024808301926020929190829003018186803b15801561332857600080fd5b505afa15801561333c573d6000803e3d6000fd5b505050506040513d602081101561335257600080fd5b50519392505050565b6000805b6001600160a01b0383166000908152603d60205260409020600201548110156133db576001600160a01b038381166000908152603d60205260409020600201805491861691839081106133ae57fe5b6000918252602090912001546001600160a01b031614156133d3576001915050610df7565b60010161335f565b5060009392505050565b60008282018381101561343f576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b9392505050565b6001600160a01b038085166000818152603d602090815260408083208890559389168252603e81528382209282529190915220829055613486858261356a565b5050505050565b60008261349c57506000610df7565b828202828482816134a957fe5b041461343f5760405162461bcd60e51b81526004018080602001828103825260218152602001806140bd6021913960400191505060405180910390fd5b600061343f83836040518060400160405280601a81526020017f536166654d6174683a206469766973696f6e206279207a65726f000000000000815250613d00565b600061343f83836040518060400160405280601e81526020017f536166654d6174683a207375627472616374696f6e206f766572666c6f770000815250613d65565b6001600160a01b039091166000908152603f6020526040902055565b6001600160a01b039091166000908152603d6020526040902060010155565b611a3e81600080600061361f565b6001600160a01b038116600090815260406020819052812060020154158015906135f757506001600160a01b03821660009081526040602081905290206001015415155b8015610df75750506001600160a01b0390811660009081526040602081905290205416151590565b604080516060810182526001600160a01b03948516815260208082019485528183019384529585166000908152958290529420935184546001600160a01b031916931692909217835551600183015551600290910155565b60005b6001600160a01b0383166000908152603d60205260409020600201548110156137a1576001600160a01b038381166000908152603d60205260409020600201805491841691839081106136c957fe5b6000918252602090912001546001600160a01b03161415613799576001600160a01b0383166000908152603d602052604090206002018054600019810190811061370f57fe5b60009182526020808320909101546001600160a01b038681168452603d909252604090922060020180549190921691908390811061374957fe5b600091825260208083209190910180546001600160a01b0319166001600160a01b039485161790559185168152603d90915260409020600201805490613793906000198301613de9565b506137a1565b60010161367a565b505050565b600080600080600080876001600160a01b031663ff653c8a886040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b03168152602001915050604080518083038186803b15801561380457600080fd5b505afa158015613818573d6000803e3d6000fd5b505050506040513d604081101561382e57600080fd5b50516001600160a01b0388166000908152603d602052604081206001015491925090613860908363ffffffff6133e516565b60365460408051631bff085760e21b81526001600160a01b038c811660048301526024820185905291519394506000939190921691636ffc215c91604480830192602092919082900301818787803b1580156138bb57600080fd5b505af11580156138cf573d6000803e3d6000fd5b505050506040513d60208110156138e557600080fd5b505160345460408051634b341aed60e01b81526001600160a01b038d811660048301529151939450911691634b341aed91602480820192602092909190829003018186803b15801561393657600080fd5b505afa15801561394a573d6000803e3d6000fd5b505050506040513d602081101561396057600080fd5b505160408051631e4e7d3560e31b81526001600160a01b038c811660048301529151929a50908c169163f273e9a89160248082019260c092909190829003018186803b1580156139af57600080fd5b505afa1580156139c3573d6000803e3d6000fd5b505050506040513d60c08110156139d957600080fd5b5080516020918201516001600160a01b038c166000908152603d90935260408320549199509550613a1190899063ffffffff6133e516565b9050613a23818463ffffffff61352816565b9650613a35898263ffffffff61352816565b8214613a725760405162461bcd60e51b81526004018080602001828103825260278152602001806140026027913960400191505060405180910390fd5b88868b6001600160a01b03167f34fcbac0073d7c3d388e51312faf357774904998eeb8fca628b9e6f65ee1cbf760405160405180910390a450935050509295509295909350565b6000805b6001600160a01b0387166000908152603d6020526040902060020154811015613cf6576001600160a01b0387166000908152603d60205260408120600201805483908110613b0757fe5b60009182526020808320909101546001600160a01b03908116808452603e835260408085208d84168087529085528186205483875294829052942054909450919291161415613b80576001600160a01b038216600090815260406020819052902060010154613b7d90829063ffffffff61352816565b90505b6000613b96896112ec848b63ffffffff61348d16565b90506000613bcd613bad8b8963ffffffff61348d16565b6112ec8a613bc1878e63ffffffff61348d16565b9063ffffffff61348d16565b9050613c39613be2838363ffffffff61352816565b603e6000876001600160a01b03166001600160a01b0316815260200190815260200160002060008e6001600160a01b03166001600160a01b03168152602001908152602001600020546133e590919063ffffffff16565b603e6000866001600160a01b03166001600160a01b0316815260200190815260200160002060008d6001600160a01b03166001600160a01b0316815260200190815260200160002081905550613cc4846113ef613c9f848661352890919063ffffffff16565b6001600160a01b0388166000908152603f60205260409020549063ffffffff6133e516565b613ce4613cd7838363ffffffff61352816565b879063ffffffff6133e516565b95505060019093019250613abd915050565b5095945050505050565b60008183613d4f5760405162461bcd60e51b81526020600482018181528351602484015283519092839260449091019190850190808383600083156108b657818101518382015260200161089e565b506000838581613d5b57fe5b0495945050505050565b60008184841115613db75760405162461bcd60e51b81526020600482018181528351602484015283519092839260449091019190850190808383600083156108b657818101518382015260200161089e565b505050900390565b604051806060016040528060006001600160a01b0316815260200160008152602001600081525090565b8154818355818111156137a1576000838152602090206137a19181019083016106ed91905b80821115613e225760008155600101613e0e565b509056fe44656c65676174654d616e616765723a204d6178696d756d2064656c656761746f727320657863656564656444656c65676174654d616e616765723a2072656d6f766544656c656761746f724c6f636b75704475726174696f6e206475726174696f6e206d7573742062652067726561746572207468616e20676f7665726e616e636520766f74696e67506572696f64202b20657865637574696f6e44656c617944656c65676174654d616e616765723a2044656c65676174696f6e206e6f74207065726d697474656420666f722053502070656e64696e6720636c61696d44656c65676174654d616e616765723a204e6f2070656e64696e67207265717565737444656c65676174654d616e616765723a2044656c656761746f72206d757374206265207374616b656420666f7220535044656c65676174654d616e616765723a207374616b696e6741646472657373206973206e6f742073657444656c65676174654d616e616765723a20556e64656c65676174652072657175657374206e6f74207065726d697474656420666f722053502070656e64696e6720636c61696d44656c65676174654d616e616765723a207365727669636550726f7669646572466163746f727941646472657373206973206e6f742073657444656c65676174654d616e616765723a2052657761726420616d6f756e74206d69736d6174636844656c65676174654d616e616765723a2050656e64696e67206c6f636b757020657870656374656444656c65676174654d616e616765723a204f6e6c792063616c6c61626c6520627920746172676574205350206f7220676f7665726e616e636544656c65676174654d616e616765723a204d696e696d756d2064656c65676174696f6e20616d6f756e74207265717569726564536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f7744656c65676174654d616e616765723a20556e64656c6567617465206e6f74207065726d697474656420666f722053502070656e64696e6720636c61696d44656c65676174654d616e616765723a205f676f7665726e616e636541646472657373206973206e6f7420612076616c696420676f7665726e616e636520636f6e747261637444656c65676174654d616e616765723a20536572766963652050726f7669646572207374616b65207265717569726564436f6e747261637420696e7374616e63652068617320616c7265616479206265656e20696e697469616c697a656444656c65676174654d616e616765723a2052657175657374656420756e64656c6567617465207374616b6520616d6f756e74206d7573742062652067726561746572207468616e207a65726f44656c65676174654d616e616765723a2050656e64696e672072656d6f76652064656c656761746f72207265717565737444656c65676174654d616e616765723a2043616e6e6f742064656372656173652067726561746572207468616e2063757272656e746c79207374616b656420666f722074686973205365727669636550726f766964657244656c65676174654d616e616765723a2052656d6f766544656c656761746f72206576616c756174696f6e2077696e646f77206578706972656444656c65676174654d616e616765723a204c6f636b7570206d757374206265206578706972656444656c65676174654d616e616765723a204e6f2070656e64696e67206c6f636b757020657870656374656444656c65676174654d616e616765723a204f6e6c792063616c6c61626c6520627920476f7665726e616e636520636f6e747261637444656c65676174654d616e616765723a2043616e6e6f7420736c617368206d6f7265207468616e20746f74616c2063757272656e746c79207374616b656444656c65676174654d616e616765723a20636c61696d734d616e6167657241646472657373206973206e6f742073657444656c65676174654d616e616765723a20636c61696d5265776172647320616d6f756e74206d69736d6174636844656c65676174654d616e616765723a20756e64656c65676174654c6f636b75704475726174696f6e206475726174696f6e206d7573742062652067726561746572207468616e20676f7665726e616e636520766f74696e67506572696f64202b20657865637574696f6e44656c6179a265627a7a72315820adf86fa6a1d687fc958fb7358f740180d41b29edab267cda0b51ec6234ee7bb064736f6c63430005110032

Deployed Bytecode Sourcemap

186974:50403:0:-;;;;8:9:-1;5:2;;;30:1;27;20:12;5:2;186974:50403:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;222927:167;;;:::i;:::-;;;;-1:-1:-1;;;;;222927:167:0;;;;;;;;;;;;;;221576;;;:::i;:::-;;;;;;;;;;;;;;;;223347:142;;;:::i;221795:145::-;;;:::i;193042:792::-;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;;;;;;193042:792:0;;;;;;;;;;;;;;;;;:::i;:::-;;211157:580;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;;;;;;211157:580:0;;;;;;;;;;:::i;217978:306::-;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;217978:306:0;-1:-1:-1;;;;;217978:306:0;;:::i;194139:2257::-;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;;;;;;194139:2257:0;;;;;;;;:::i;205402:4445::-;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;205402:4445:0;;;;;;-1:-1:-1;;;;;205402:4445:0;;:::i;221247:265::-;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;;;;;;221247:265:0;;;;;;;;;;:::i;215584:305::-;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;215584:305:0;;:::i;198643:898::-;;;:::i;210041:924::-;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;;;;;;210041:924:0;;;;;;;;;;:::i;222720:143::-;;;:::i;219902:207::-;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;219902:207:0;-1:-1:-1;;;;;219902:207:0;;:::i;41234:80::-;;;:::i;222230:177::-;;;:::i;219618:200::-;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;219618:200:0;-1:-1:-1;;;;;219618:200:0;;:::i;215166:275::-;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;215166:275:0;;:::i;220600:344::-;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;220600:344:0;-1:-1:-1;;;;;220600:344:0;;:::i;:::-;;;;-1:-1:-1;;;;;220600:344:0;;;;;;;;;;;;;;;;;;;;;;;;;223149:149;;;:::i;222495:173::-;;;:::i;196685:1888::-;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;;;;;;196685:1888:0;;;;;;;;:::i;218483:312::-;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;218483:312:0;-1:-1:-1;;;;;218483:312:0;;:::i;219357:187::-;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;219357:187:0;-1:-1:-1;;;;;219357:187:0;;:::i;221995:157::-;;;:::i;216472:302::-;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;216472:302:0;;:::i;220207:237::-;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;;;;;;220207:237:0;;;;;;;;;;:::i;216964:351::-;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;216964:351:0;-1:-1:-1;;;;;216964:351:0;;:::i;212007:2598::-;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;;;;;;212007:2598:0;;;;;;;;;;:::i;214740:299::-;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;214740:299:0;;:::i;202865:2261::-;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;202865:2261:0;-1:-1:-1;;;;;202865:2261:0;;:::i;217496:276::-;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;217496:276:0;-1:-1:-1;;;;;217496:276:0;;:::i;216018:314::-;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;216018:314:0;;:::i;199715:2857::-;;;:::i;219044:183::-;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;219044:183:0;-1:-1:-1;;;;;219044:183:0;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8:100:-1;33:3;30:1;27:10;8:100;;;90:11;;;84:18;71:11;;;64:39;52:2;45:10;8:100;;;12:14;219044:183:0;;;;;;;;;;;;;;;;;222927:167;222994:7;223014:23;:21;:23::i;:::-;-1:-1:-1;223057:29:0;;-1:-1:-1;;;;;223057:29:0;222927:167;;:::o;221576:::-;221643:7;221668:23;:21;:23::i;:::-;-1:-1:-1;221711:24:0;;221576:167;:::o;223347:142::-;223399:7;223424:23;:21;:23::i;:::-;-1:-1:-1;223467:14:0;;-1:-1:-1;;;;;223467:14:0;223347:142;:::o;221795:145::-;221851:7;221876:23;:21;:23::i;:::-;-1:-1:-1;221919:13:0;;221795:145;:::o;193042:792::-;1118:12;;;;;;;;:31;;;1134:15;:13;:15::i;:::-;1118:47;;;-1:-1:-1;1154:11:0;;;;1153:12;1118:47;1110:106;;;;-1:-1:-1;;;1110:106:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1225:19;1248:12;;;;;;1247:13;1267:83;;;;1296:12;:19;;-1:-1:-1;;;;1296:19:0;;;;;1324:18;1311:4;1324:18;;;1267:83;193218:44;193243:18;193218:24;:44::i;:::-;193273:11;:42;;-1:-1:-1;;;;;;193273:42:0;-1:-1:-1;;;;;193273:42:0;;;;;193342:3;193326:13;:19;193438:21;193416:19;:43;193470:28;:26;:28::i;:::-;193511:58;193543:25;193511:31;:58::i;:::-;193666:43;193703:5;193666:36;:43::i;:::-;193822:4;193792:27;:34;1368:57;;;;1412:5;1397:20;;-1:-1:-1;;1397:20:0;;;1368:57;193042:792;;;;:::o;211157:580::-;211283:10;-1:-1:-1;;;;;211283:30:0;;;;:65;;-1:-1:-1;211331:17:0;;;;;-1:-1:-1;;;;;211331:17:0;211317:10;:31;211283:65;211363:24;;;;;;;;;;;;;;;;;211261:137;;;;;-1:-1:-1;;;211261:137:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;23:1:-1;8:100;33:3;30:1;27:10;8:100;;;90:11;;;84:18;71:11;;;64:39;52:2;45:10;8:100;;;12:14;211261:137:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;;211431:41:0;;;;;;;:23;:41;;;;;;;;:53;;;;;;;;;;211409:143;;;;-1:-1:-1;;;211409:143:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;211595:41:0;;;211651:1;211595:41;;;:23;:41;;;;;;;;:53;;;;;;;;;;;;;:57;;;211668:61;;;211651:1;211668:61;211157:580;;:::o;217978:306::-;218060:23;:21;:23::i;:::-;218118:17;;;;;;;;;-1:-1:-1;;;;;218118:17:0;-1:-1:-1;;;;;218104:31:0;:10;-1:-1:-1;;;;;218104:31:0;;218137:21;;;;;;;;;;;;;;;;;218096:63;;;;;-1:-1:-1;;;218096:63:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;27:10:-1;;8:100;;90:11;;;84:18;71:11;;;64:39;52:2;45:10;8:100;;218096:63:0;-1:-1:-1;218170:29:0;:42;;-1:-1:-1;;;;;;218170:42:0;-1:-1:-1;;;;;218170:42:0;;;;;;;;218228:48;;;;-1:-1:-1;;218228:48:0;217978:306;:::o;194139:2257::-;194241:7;194266:23;:21;:23::i;:::-;194300:29;:27;:29::i;:::-;194340:44;:42;:44::i;:::-;194395:35;:33;:35::i;:::-;194466:24;194480:9;194466:13;:24::i;:::-;194465:25;194443:137;;;;-1:-1:-1;;;194443:137:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;194666:14;;194749:113;;;-1:-1:-1;;;194749:113:0;;-1:-1:-1;;;;;194749:113:0;;;;;;;194611:10;194749:113;;;;;;;;;;;;;;194611:10;;194666:14;;;;194749:32;;:113;;;;;194591:17;;194749:113;;;;;;;194591:17;194666:14;194749:113;;;5:2:-1;;;;30:1;27;20:12;5:2;194749:113:0;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;194749:113:0;;;;194937:43;194959:9;194970;194937:21;:43::i;:::-;194932:365;;-1:-1:-1;;;;;195052:25:0;;;;;;;:14;:25;;;;;;;:36;;27:10:-1;;39:1;23:18;;45:23;;195052:52:0;;;;;;;;;;;-1:-1:-1;;;;;;195052:52:0;;;;;;;;;;;195192:13;;195145:25;;;:43;;:60;;195119:166;;;;-1:-1:-1;;;195119:166:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;195695:25:0;;;;;;:14;:25;;;;;:45;195611:273;;195647:9;;195671;;195695:58;;195745:7;195695:58;:49;:58;:::i;:::-;-1:-1:-1;;;;;195768:23:0;;;;;;;:12;:23;;;;;;;;:34;;;;;;;;;;:47;;195807:7;195768:47;:38;:47;:::i;:::-;-1:-1:-1;;;;;195830:30:0;;;;;;:19;:30;;;;;;:43;;195865:7;195830:43;:34;:43;:::i;:::-;195611:21;:273::i;:::-;195957:19;;-1:-1:-1;;;;;195919:23:0;;;;;;;:12;:23;;;;;;;;:34;;;;;;;;;;;;;195991:24;;;;;;;;;;;;195919:57;-1:-1:-1;195919:57:0;;195991:24;;;;;;195897:129;;;;;-1:-1:-1;;;195897:129:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;27:10:-1;;8:100;;90:11;;;84:18;71:11;;;64:39;52:2;45:10;8:100;;195897:129:0;-1:-1:-1;196105:29:0;;196068:116;;;-1:-1:-1;;;196068:116:0;;-1:-1:-1;;;;;196068:116:0;;;;;;;;;196105:29;;;;;196068:105;;:116;;;;;196105:29;;196068:116;;;;;;;196105:29;196068:116;;;5:2:-1;;;;30:1;27;20:12;5:2;196068:116:0;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;196068:116:0;;;;196287:7;196263:9;-1:-1:-1;;;;;196202:103:0;196239:9;-1:-1:-1;;;;;196202:103:0;;;;;;;;;;;-1:-1:-1;;;;;;196354:23:0;;;;;;;:12;:23;;;;;;;;:34;;;;;;;;;;;-1:-1:-1;194139:2257:0;;;;;:::o;205402:4445::-;205487:23;:21;:23::i;:::-;205521:29;:27;:29::i;:::-;205561:44;:42;:44::i;:::-;205640:17;;;;;;;;;-1:-1:-1;;;;;205640:17:0;-1:-1:-1;;;;;205626:31:0;:10;-1:-1:-1;;;;;205626:31:0;;205659:21;;;;;;;;;;;;;;;;;205618:63;;;;;-1:-1:-1;;;205618:63:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;27:10:-1;;8:100;;90:11;;;84:18;71:11;;;64:39;52:2;45:10;8:100;;205618:63:0;-1:-1:-1;205728:14:0;;205812:29;;205951:45;;;-1:-1:-1;;;205951:45:0;;-1:-1:-1;;;;;205951:45:0;;;;;;;;;205728:14;;;;205812:29;;;;;-1:-1:-1;;205728:14:0;;205951:30;;:45;;;;;;;;;;;;;;205728:14;205951:45;;;5:2:-1;;;;30:1;27;20:12;5:2;205951:45:0;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;205951:45:0;;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;205951:45:0;;-1:-1:-1;206030:40:0;;;;206007:154;;;;-1:-1:-1;;;206007:154:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;206269:55;;;-1:-1:-1;;;;;;206269:55:0;;-1:-1:-1;;;;;206269:55:0;;;;;;;;;206243:21;;206269:40;;;;;;:55;;;;;;;;;;;;:40;:55;;;5:2:-1;;;;30:1;27;20:12;5:2;206269:55:0;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;206269:55:0;;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;206269:55:0;;-1:-1:-1;206339:17:0;;206335:101;;206373:9;-1:-1:-1;;;;;206373:36:0;;206410:13;206373:51;;;;;;;;;;;;;-1:-1:-1;;;;;206373:51:0;-1:-1:-1;;;;;206373:51:0;;;;;;;;;;;;;;;;;;;;8:9:-1;5:2;;;30:1;27;20:12;5:2;206373:51:0;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;206373:51:0;;;;206335:101;206499:31;206554:9;-1:-1:-1;;;;;206554:35:0;;206590:13;206554:50;;;;;;;;;;;;;-1:-1:-1;;;;;206554:50:0;-1:-1:-1;;;;;206554:50:0;;;;;;;;;;;;;;;;;;;8:9:-1;5:2;;;30:1;27;20:12;5:2;206554:50:0;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;206554:50:0;;;;;;;13:3:-1;8;5:12;2:2;;;30:1;27;20:12;2:2;-1:-1;206554:50:0;;-1:-1:-1;206648:27:0;206626:125;;;;-1:-1:-1;;;206626:125:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;206894:15;-1:-1:-1;;;;;206894:21:0;;206916:7;206925:13;206894:45;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;206894:45:0;-1:-1:-1;;;;;206894:45:0;;;;;;;;;;;;;;;;;;;;;8:9:-1;5:2;;;30:1;27;20:12;5:2;206894:45:0;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;206894:45:0;;;;206950:39;206992:15;-1:-1:-1;;;;;206992:30:0;;207023:13;206992:45;;;;;;;;;;;;;-1:-1:-1;;;;;206992:45:0;-1:-1:-1;;;;;206992:45:0;;;;;;;;;;;;;;;;;;;8:9:-1;5:2;;;30:1;27;20:12;5:2;206992:45:0;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;206992:45:0;;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;206992:45:0;207084:62;;206992:45;;-1:-1:-1;206992:45:0;;207105:7;;-1:-1:-1;;;;;207084:62:0;;;;;;;;207159:35;;207362:1779;-1:-1:-1;;;;;207386:29:0;;;;;;:14;:29;;;;;:40;;:47;207382:51;;207362:1779;;;-1:-1:-1;;;;;207475:29:0;;207455:17;207475:29;;;:14;:29;;;;;:40;;:43;;207516:1;;207475:43;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;207475:43:0;;;207565:23;;;:12;:23;;;;;;:38;;;;;;;;;;;;207475:43;;-1:-1:-1;207565:38:0;207645:124;207739:29;207661:58;:31;207565:38;207661:58;:35;:58;:::i;:::-;207645:93;:124;:93;:124;:::i;:::-;207618:151;-1:-1:-1;207928:87:0;207971:43;:21;207618:151;207971:43;:25;:43;:::i;:::-;207928:12;:23;207941:9;-1:-1:-1;;;;;207928:23:0;-1:-1:-1;;;;;207928:23:0;;;;;;;;;;;;:38;207952:13;-1:-1:-1;;;;;207928:38:0;-1:-1:-1;;;;;207928:38:0;;;;;;;;;;;;;:42;;:87;;;;:::i;:::-;207868:12;:23;207881:9;-1:-1:-1;;;;;207868:23:0;-1:-1:-1;;;;;207868:23:0;;;;;;;;;;;;:38;207892:13;-1:-1:-1;;;;;207868:38:0;-1:-1:-1;;;;;207868:38:0;;;;;;;;;;;;:162;;;;208094:167;208139:9;208167:79;208202:43;208228:16;208202:21;:25;;:43;;;;:::i;:::-;-1:-1:-1;;;;;208167:30:0;;;;;;:19;:30;;;;;;;:79;:34;:79;:::i;:::-;208094:26;:167::i;:::-;208370:76;208402:43;:21;208428:16;208402:43;:25;:43;:::i;:::-;208370:27;;:76;:31;:76;:::i;:::-;-1:-1:-1;;;;;208617:29:0;;;;;;:18;:29;;;;;;:36;;;208321:140;;-1:-1:-1;208617:41:0;208613:517;;-1:-1:-1;;;;;208699:29:0;;;208679:17;208699:29;;;:18;:29;;;;;;;:45;;;208787:36;;;;208699:45;;208974:25;;;:14;:25;;;;;;;:44;;208699:45;;208787:36;208885:171;;208699:45;;208974:63;;208787:36;208974:63;:48;:63;:::i;:::-;208885:34;:171::i;:::-;209075:39;209104:9;209075:28;:39::i;:::-;208613:517;;;-1:-1:-1;;;207435:3:0;;207362:1779;;;-1:-1:-1;;;;;;209266:29:0;;;;;;:14;:29;;;;;:49;:82;;209320:27;209266:82;:53;:82;:::i;:::-;-1:-1:-1;;;;;209199:29:0;;;;;;:14;:29;;;;;:160;;;;209475:66;:29;209509:31;209475:66;:33;:66;:::i;:::-;209431:121;-1:-1:-1;209563:37:0;209618:51;209431:121;209641:27;209618:51;:22;:51;:::i;:::-;209563:117;-1:-1:-1;;;;;;209691:36:0;;;209742:13;209770:58;:23;209563:117;209770:58;:27;:58;:::i;:::-;209691:148;;;;;;;;;;;;;-1:-1:-1;;;;;209691:148:0;-1:-1:-1;;;;;209691:148:0;;;;;;;;;;;;;;;;;;;;;;;;;;8:9:-1;5:2;;;30:1;27;20:12;5:2;209691:148:0;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;209691:148:0;;;;205402:4445;;;;;;;;;;;:::o;221247:265::-;221383:7;221408:23;:21;:23::i;:::-;-1:-1:-1;;;;;;221451:41:0;;;;;;;:23;:41;;;;;;;;:53;;;;;;;;;;;;;221247:265::o;215584:305::-;215669:23;:21;:23::i;:::-;215727:17;;;;;;;;;-1:-1:-1;;;;;215727:17:0;-1:-1:-1;;;;;215713:31:0;:10;-1:-1:-1;;;;;215713:31:0;;215746:21;;;;;;;;;;;;;;;;;215705:63;;;;;-1:-1:-1;;;215705:63:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;27:10:-1;;8:100;;90:11;;;84:18;71:11;;;64:39;52:2;45:10;8:100;;215705:63:0;-1:-1:-1;215781:19:0;:42;;;215839;;215803:20;;215839:42;;;;;215584:305;:::o;198643:898::-;198703:23;:21;:23::i;:::-;198759:10;198849:38;198759:10;198849:27;:38::i;:::-;198827:128;;;;-1:-1:-1;;;198827:128:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;198990:29:0;;;198966:21;198990:29;;;:18;:29;;;;;;;:36;;;;;199061:45;;;;;199282:29;;;:14;:29;;;;;;;:48;;;;199205:155;;199061:45;;199282:67;;198990:36;199282:67;:52;:67;:::i;199205:155::-;199406:39;199435:9;199406:28;:39::i;:::-;199519:13;199504;-1:-1:-1;;;;;199461:72:0;199493:9;-1:-1:-1;;;;;199461:72:0;;;;;;;;;;;198643:898;;;:::o;210041:924::-;210139:23;:21;:23::i;:::-;210197:10;-1:-1:-1;;;;;210197:30:0;;;;:65;;-1:-1:-1;210245:17:0;;;;;-1:-1:-1;;;;;210245:17:0;210231:10;:31;210197:65;210277:24;;;;;;;;;;;;;;;;;210175:137;;;;;-1:-1:-1;;;210175:137:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;27:10:-1;;8:100;;90:11;;;84:18;71:11;;;64:39;52:2;45:10;8:100;;210175:137:0;-1:-1:-1;;;;;;210347:41:0;;;;;;;:23;:41;;;;;;;;:53;;;;;;;;;;:58;210325:157;;;;-1:-1:-1;;;210325:157:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;210517:51;210539:10;210551:16;210517:21;:51::i;:::-;210583:21;;;;;;;;;;;;;;;;;210495:120;;;;;-1:-1:-1;;;210495:120:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;27:10:-1;;8:100;;90:11;;;84:18;71:11;;;64:39;52:2;45:10;8:100;;210495:120:0;-1:-1:-1;210740:29:0;;-1:-1:-1;;;;;210654:41:0;;;;;;;:23;:41;;;;;;;;:53;;;;;;;;;;;;;210725:12;:44;;;210654:126;;;;210798:159;;;210654:41;210798:159;210041:924;;:::o;222720:143::-;222775:7;222795:23;:21;:23::i;:::-;-1:-1:-1;222838:17:0;;;;;-1:-1:-1;;;;;222838:17:0;;222720:143::o;219902:207::-;219995:7;220020:23;:21;:23::i;:::-;-1:-1:-1;;;;;;220063:19:0;;;;;:14;:19;;;;;:38;;;;219902:207::o;41234:80::-;1118:12;;;;;;;;:31;;;1134:15;:13;:15::i;:::-;1118:47;;;-1:-1:-1;1154:11:0;;;;1153:12;1118:47;1110:106;;;;-1:-1:-1;;;1110:106:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1225:19;1248:12;;;;;;1247:13;1267:83;;;;1296:12;:19;;-1:-1:-1;;;;1296:19:0;;;;;1324:18;1311:4;1324:18;;;1267:83;41286:13;:20;;-1:-1:-1;;41286:20:0;41302:4;41286:20;;;1368:57;;;;1412:5;1397:20;;-1:-1:-1;;1397:20:0;;;1368:57;41234:80;:::o;222230:177::-;222302:7;222327:23;:21;:23::i;:::-;-1:-1:-1;222370:29:0;;222230:177;:::o;219618:200::-;219703:7;219728:23;:21;:23::i;:::-;-1:-1:-1;;;;;;219771:19:0;;;;;:14;:19;;;;;:39;;219618:200::o;215166:275::-;215239:23;:21;:23::i;:::-;215297:17;;;;;;;;;-1:-1:-1;;;;;215297:17:0;-1:-1:-1;;;;;215283:31:0;:10;-1:-1:-1;;;;;215283:31:0;;215316:21;;;;;;;;;;;;;;;;;215275:63;;;;;-1:-1:-1;;;215275:63:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;27:10:-1;;8:100;;90:11;;;84:18;71:11;;;64:39;52:2;45:10;8:100;;215275:63:0;-1:-1:-1;215351:13:0;:30;;;215397:36;;215367:14;;215397:36;;;;;215166:275;:::o;220600:344::-;220685:14;220701;220717:25;220760:23;:21;:23::i;:::-;220796:33;;:::i;:::-;-1:-1:-1;;;;;;;;;220832:30:0;;;;;;;:18;:30;;;;;;;;220796:66;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;220600:344::o;223149:149::-;223207:7;223227:23;:21;:23::i;:::-;-1:-1:-1;223270:20:0;;-1:-1:-1;;;;;223270:20:0;223149:149;:::o;222495:173::-;222565:7;222590:23;:21;:23::i;:::-;-1:-1:-1;222633:27:0;;222495:173;:::o;196685:1888::-;196794:7;196819:23;:21;:23::i;:::-;196853:35;:33;:35::i;:::-;196933:1;196923:7;:11;196901:137;;;;-1:-1:-1;;;196901:137:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;197072:22;197086:7;197072:13;:22::i;:::-;197071:23;197049:143;;;;-1:-1:-1;;;197049:143:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;197223:10;197266:41;197223:10;197299:7;197266:21;:41::i;:::-;197322:21;;;;;;;;;;;;;;;;;197244:110;;;;;-1:-1:-1;;;197244:110:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;27:10:-1;;8:100;;90:11;;;84:18;71:11;;;64:39;52:2;45:10;8:100;;197244:110:0;;197440:38;197468:9;197440:27;:38::i;:::-;197439:39;197417:132;;;;-1:-1:-1;;;197417:132:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;197627:23:0;;;197594:30;197627:23;;;:12;:23;;;;;;;;:32;;;;;;;;;;197692:33;;;;197670:170;;;;-1:-1:-1;;;197670:170:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;197955:25;197983:42;198000:24;;197983:12;:16;;:42;;;;:::i;:::-;197955:70;;198036:140;198080:9;198104:7;198126;198148:17;198036:29;:140::i;:::-;-1:-1:-1;;;;;198346:23:0;;;;;;:14;:23;;;;;:42;;;198275:137;;198324:7;;198346:55;;198393:7;198346:55;:46;:55;:::i;198275:137::-;198475:7;198466;-1:-1:-1;;;;;198430:72:0;198455:9;-1:-1:-1;;;;;198430:72:0;;198484:17;198430:72;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;198520:23:0;;;;;;;:12;:23;;;;;;;;:32;;;;;;;;;;:45;;198557:7;198520:45;:36;:45;:::i;:::-;198513:52;196685:1888;-1:-1:-1;;;;;;196685:1888:0:o;218483:312::-;218567:23;:21;:23::i;:::-;218625:17;;;;;;;;;-1:-1:-1;;;;;218625:17:0;-1:-1:-1;;;;;218611:31:0;:10;-1:-1:-1;;;;;218611:31:0;;218644:21;;;;;;;;;;;;;;;;;218603:63;;;;;-1:-1:-1;;;218603:63:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;27:10:-1;;8:100;;90:11;;;84:18;71:11;;;64:39;52:2;45:10;8:100;;218603:63:0;-1:-1:-1;218677:20:0;:44;;-1:-1:-1;;;;;;218677:44:0;-1:-1:-1;;;;;218677:44:0;;;;;;;;218737:50;;;;-1:-1:-1;;218737:50:0;218483:312;:::o;219357:187::-;219437:7;219462:23;:21;:23::i;:::-;-1:-1:-1;;;;;;219505:31:0;;;;;:19;:31;;;;;;;219357:187::o;221995:157::-;222057:7;222082:23;:21;:23::i;:::-;-1:-1:-1;222125:19:0;;221995:157;:::o;216472:302::-;216554:23;:21;:23::i;:::-;216612:17;;;;;;;;;-1:-1:-1;;;;;216612:17:0;-1:-1:-1;;;;;216598:31:0;:10;-1:-1:-1;;;;;216598:31:0;;216631:21;;;;;;;;;;;;;;;;;216590:63;;;;;-1:-1:-1;;;216590:63:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;27:10:-1;;8:100;;90:11;;;84:18;71:11;;;64:39;52:2;45:10;8:100;;216590:63:0;-1:-1:-1;216666:27:0;:39;;;216721:45;;216696:9;;216721:45;;;;;216472:302;:::o;220207:237::-;220326:7;220351:23;:21;:23::i;:::-;-1:-1:-1;;;;;;220394:24:0;;;;;;;:12;:24;;;;;;;;:42;;;;;;;;;;;;;220207:237::o;216964:351::-;217042:23;:21;:23::i;:::-;217100:17;;;;;;;;;-1:-1:-1;;;;;217100:17:0;-1:-1:-1;;;;;217086:31:0;:10;-1:-1:-1;;;;;217086:31:0;;217119:21;;;;;;;;;;;;;;;;;217078:63;;;;;-1:-1:-1;;;217078:63:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;27:10:-1;;8:100;;90:11;;;84:18;71:11;;;64:39;52:2;45:10;8:100;;217078:63:0;;217154:44;217179:18;217154:24;:44::i;:::-;217209:17;:38;;-1:-1:-1;;;;;;217209:38:0;;-1:-1:-1;;;;;217209:38:0;;;;;;;;;;;;217263:44;;;;-1:-1:-1;;217263:44:0;216964:351;:::o;212007:2598::-;212098:23;:21;:23::i;:::-;212132:29;:27;:29::i;:::-;212196:10;-1:-1:-1;;;;;212196:30:0;;;;:65;;-1:-1:-1;212244:17:0;;;;;-1:-1:-1;;;;;212244:17:0;212230:10;:31;212196:65;212276:24;;;;;;;;;;;;;;;;;212174:137;;;;;-1:-1:-1;;;212174:137:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;27:10:-1;;8:100;;90:11;;;84:18;71:11;;;64:39;52:2;45:10;8:100;;212174:137:0;-1:-1:-1;;;;;;212346:41:0;;;;;;;:23;:41;;;;;;;;:53;;;;;;;;;;212324:143;;;;-1:-1:-1;;;212324:143:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;212558:41:0;;;;;;;:23;:41;;;;;;;;:53;;;;;;;;;;212542:12;:69;;212520:158;;;;-1:-1:-1;;;212520:158:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;212834:27;;-1:-1:-1;;;;;212778:41:0;;;;;;;:23;:41;;;;;;;;:53;;;;;;;;;;:83;212763:12;:98;212741:206;;;;-1:-1:-1;;;212741:206:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;212984:24:0;;;212960:21;212984:24;;;:12;:24;;;;;;;;:42;;;;;;;;;;;;213102:14;;213094:137;;-1:-1:-1;;;213094:137:0;;;;;;;;;;;;;;;;;;;;;;;;212984:42;;213102:14;;;;;213094:42;;:137;;;;;212960:21;;213094:137;;;;;;212960:21;213102:14;213094:137;;;5:2:-1;;;;30:1;27;20:12;5:2;213094:137:0;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;-1:-1;;;;;;;;213575:32:0;;;;;;:14;:32;;;;;:52;213483:315;;-1:-1:-1;213519:10:0;;213544:16;;213575:71;;213632:13;213575:71;:56;:71;:::i;:::-;-1:-1:-1;;;;;213661:24:0;;;;;;;:12;:24;;;;;;;;:42;;;;;;;;;;:61;;213708:13;213661:61;:46;:61;:::i;:::-;-1:-1:-1;;;;;213737:31:0;;;;;;:19;:31;;;;;;:50;;213773:13;213737:50;:35;:50;:::i;213483:315::-;213829:39;213857:10;213829:27;:39::i;:::-;:122;;;;-1:-1:-1;;;;;;213885:30:0;;;;;;;:18;:30;;;;;;:46;;;:66;;;;213829:122;213811:482;;;-1:-1:-1;;;;;214173:30:0;;;;;;;:18;:30;;;;;;;:37;;;;;214117:32;;;;;:14;:32;;;;;:51;;214029:197;;214082:16;;214117:94;;;:55;:94;:::i;214029:197::-;214241:40;214270:10;214241:28;:40::i;:::-;214348:55;214374:16;214392:10;214348:25;:55::i;:::-;-1:-1:-1;;;;;214448:41:0;;;214504:1;214448:41;;;:23;:41;;;;;;;;:53;;;;;;;;;;;;;:57;;;214521:76;214583:13;;214448:53;:41;214521:76;;;212007:2598;;;:::o;214740:299::-;214819:23;:21;:23::i;:::-;214877:17;;;;;;;;;-1:-1:-1;;;;;214877:17:0;-1:-1:-1;;;;;214863:31:0;:10;-1:-1:-1;;;;;214863:31:0;;214896:21;;;;;;;;;;;;;;;;;214855:63;;;;;-1:-1:-1;;;214855:63:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;27:10:-1;;8:100;;90:11;;;84:18;71:11;;;64:39;52:2;45:10;8:100;;214855:63:0;;214931:42;214963:9;214931:31;:42::i;:::-;214989;;215021:9;;214989:42;;;;;214740:299;:::o;202865:2261::-;202933:23;:21;:23::i;:::-;202967:29;:27;:29::i;:::-;203007:44;:42;:44::i;:::-;203062:35;:33;:35::i;:::-;203168:29;;-1:-1:-1;;;;;203168:29:0;203110:32;;;;;203532:50;203168:29;203565:16;203532:21;:50::i;:::-;203320:262;;;;;;;;;;203742:12;203758:1;203742:17;203738:56;;;203776:7;;;;;;;;203738:56;203806:35;203844:212;203885:16;203916;203947:12;203974:11;204000:9;-1:-1:-1;;;;;204000:43:0;;:45;;;;;;;;;;;;;;;;;;;;;;8:9:-1;5:2;;;30:1;27;20:12;5:2;204000:45:0;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;204000:45:0;;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;204000:45:0;203844:26;:212::i;:::-;-1:-1:-1;;;;;204185:32:0;;;;;;:14;:32;;;;;:52;203806:250;;-1:-1:-1;204185:85:0;;203806:250;204185:85;:56;:85;:::i;:::-;-1:-1:-1;;;;;204115:32:0;;;;;;:14;:32;;;;;:166;;;;204581:45;:12;204598:27;204581:45;:16;:45;:::i;:::-;204557:69;-1:-1:-1;204711:27:0;204741:42;:23;204557:69;204741:42;:27;:42;:::i;:::-;-1:-1:-1;;;;;204867:32:0;;;;;;:14;:32;;;;;:52;204711:72;;-1:-1:-1;204843:77:0;;204711:72;;204843:77;:23;:77;:::i;:::-;204818:21;:102;204796:197;;;;-1:-1:-1;;;204796:197:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;205006:9;-1:-1:-1;;;;;205006:36:0;;205057:16;205088:19;205006:112;;;;;;;;;;;;;-1:-1:-1;;;;;205006:112:0;-1:-1:-1;;;;;205006:112:0;;;;;;;;;;;;;;;;;;;;;;;;;;8:9:-1;5:2;;;30:1;27;20:12;5:2;205006:112:0;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;205006:112:0;;;;202865:2261;;;;;;;;;;:::o;217496:276::-;217568:23;:21;:23::i;:::-;217626:17;;;;;;;;;-1:-1:-1;;;;;217626:17:0;-1:-1:-1;;;;;217612:31:0;:10;-1:-1:-1;;;;;217612:31:0;;217645:21;;;;;;;;;;;;;;;;;217604:63;;;;;-1:-1:-1;;;217604:63:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;27:10:-1;;8:100;;90:11;;;84:18;71:11;;;64:39;52:2;45:10;8:100;;217604:63:0;-1:-1:-1;217678:14:0;:32;;-1:-1:-1;;;;;;217678:32:0;-1:-1:-1;;;;;217678:32:0;;;;;;;;217726:38;;;;-1:-1:-1;;217726:38:0;217496:276;:::o;216018:314::-;216102:23;:21;:23::i;:::-;216160:17;;;;;;;;;-1:-1:-1;;;;;216160:17:0;-1:-1:-1;;;;;216146:31:0;:10;-1:-1:-1;;;;;216146:31:0;;216179:21;;;;;;;;;;;;;;;;;216138:63;;;;;-1:-1:-1;;;216138:63:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;27:10:-1;;8:100;;90:11;;;84:18;71:11;;;64:39;52:2;45:10;8:100;;216138:63:0;;216214:47;216251:9;216214:36;:47::i;:::-;216277;;216314:9;;216277:47;;;;;216018:314;:::o;199715:2857::-;199760:7;199780:23;:21;:23::i;:::-;199814:29;:27;:29::i;:::-;199854:44;:42;:44::i;:::-;199909:35;:33;:35::i;:::-;199977:10;200069:38;199977:10;200069:27;:38::i;:::-;200047:128;;;;-1:-1:-1;;;200047:128:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;200256:29:0;;;;;;:18;:29;;;;;;:47;;;200307:12;-1:-1:-1;200256:63:0;200234:152;;;;-1:-1:-1;;;200234:152:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;200499:29:0;;;;;;;:18;:29;;;;;;:45;200485:60;;200499:45;200485:13;:60::i;:::-;200484:61;200462:173;;;;-1:-1:-1;;;200462:173:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;200674:29:0;;;200648:23;200674:29;;;:18;:29;;;;;;;:45;;;200754:36;;;;200868:14;;200860:135;;-1:-1:-1;;;200860:135:0;;200674:45;;;200860:135;;;;;;;;;;;;;;;;;;;;;200674:45;;200754:36;;200868:14;;;200860:42;;:135;;;;;;;;;;200648:23;200868:14;200860:135;;;5:2:-1;;;;30:1;27;20:12;5:2;200860:135:0;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;-1:-1;;;;;;;;201339:31:0;;;;;;:14;:31;;;;;:51;201249:309;;-1:-1:-1;201285:9:0;;201309:15;;201339:70;;201395:13;201339:70;:55;:70;:::i;:::-;-1:-1:-1;;;;;201424:23:0;;;;;;;:12;:23;;;;;;;;:40;;;;;;;;;;:59;;201469:13;201424:59;:44;:59;:::i;:::-;-1:-1:-1;;;;;201498:30:0;;;;;;:19;:30;;;;;;:49;;201533:13;201498:49;:34;:49;:::i;201249:309::-;201638:19;;-1:-1:-1;;;;;201594:23:0;;;;;;;:12;:23;;;;;;;;:40;;;;;;;;;;:63;;;:126;;-1:-1:-1;;;;;;201675:23:0;;;;;;;:12;:23;;;;;;;;:40;;;;;;;;;;:45;201594:126;201736:24;;;;;;;;;;;;;;;;;201571:200;;;;;-1:-1:-1;;;201571:200:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;27:10:-1;;8:100;;90:11;;;84:18;71:11;;;64:39;52:2;45:10;8:100;;201571:200:0;-1:-1:-1;;;;;;201860:23:0;;;;;;;:12;:23;;;;;;;;:40;;;;;;;;;;201856:131;;201922:53;201948:15;201965:9;201922:25;:53::i;:::-;-1:-1:-1;;;;;202166:31:0;;;;;;:14;:31;;;;;:50;;;202087:159;;202136:15;;202166:69;;202221:13;202166:69;:54;:69;:::i;202087:159::-;202294:39;202323:9;202294:28;:39::i;:::-;202451:13;202421:15;-1:-1:-1;;;;;202351:124:0;202397:9;-1:-1:-1;;;;;202351:124:0;;;;;;;;;;;-1:-1:-1;;;;;;202524:23:0;;;;;;;:12;:23;;;;;;;;:40;;;;;;;;;;;;;-1:-1:-1;199715:2857:0;:::o;219044:183::-;219112:16;219146:23;:21;:23::i;:::-;-1:-1:-1;;;;;219189:19:0;;;;;;:14;:19;;;;;;;;;:30;;219182:37;;;;;;;;;;;;;;;;;219189:30;;219182:37;;219189:30;219182:37;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;219182:37:0;;;;;;;;;;;;;;;;;;;;;;;219044:183;;;:::o;41506:119::-;41572:13;;41595:21;;;;;;;;;;;;;;;;;;;41572:13;;:21;;:13;:21;41564:53;;;;-1:-1:-1;;;41564:53:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;27:10:-1;;8:100;;90:11;;;84:18;71:11;;;64:39;52:2;45:10;8:100;;1519:508:0;1936:4;1982:17;2014:7;1519:508;:::o;233351:319::-;233466:18;-1:-1:-1;;;;;233455:50:0;;:52;;;;;;;;;;;;;;;;;;;;;;8:9:-1;5:2;;;30:1;27;20:12;5:2;233455:52:0;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;233455:52:0;;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;233455:52:0;:60;;233511:4;233455:60;233433:180;;;;-1:-1:-1;;;233433:180:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;233624:17;:38;;-1:-1:-1;;;;;233624:38:0;;;;;-1:-1:-1;;;;;;233624:38:0;;;;;;;;;233351:319::o;234488:434::-;234568:21;234603:17;;;;;;;;;-1:-1:-1;;;;;234603:17:0;234568:53;;234697:10;-1:-1:-1;;;;;234697:28:0;;:30;;;;;;;;;;;;;;;;;;;;;;8:9:-1;5:2;;;30:1;27;20:12;5:2;234697:30:0;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;234697:30:0;;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;234697:30:0;234666:28;;;-1:-1:-1;;;234666:28:0;;;;-1:-1:-1;;;;;234666:26:0;;;;;:28;;;;;234697:30;;234666:28;;;;;;;:26;:28;;;5:2:-1;;;;30:1;27;20:12;5:2;234666:28:0;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;234666:28:0;;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;234666:28:0;:61;234654:73;;234632:235;;;;-1:-1:-1;;;234632:235:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;234878:24:0;:36;234488:434::o;233857:449::-;233942:21;233977:17;;;;;;;;;-1:-1:-1;;;;;233977:17:0;233942:53;;234071:10;-1:-1:-1;;;;;234071:28:0;;:30;;;;;;;;;;;;;;;;;;;;;;8:9:-1;5:2;;;30:1;27;20:12;5:2;234071:30:0;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;234071:30:0;;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;234071:30:0;234040:28;;;-1:-1:-1;;;234040:28:0;;;;-1:-1:-1;;;;;234040:26:0;;;;;:28;;;;;234071:30;;234040:28;;;;;;;:26;:28;;;5:2:-1;;;;30:1;27;20:12;5:2;234040:28:0;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;234040:28:0;;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;234040:28:0;:61;234028:73;;234006:240;;;;-1:-1:-1;;;234006:240:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;234257:29:0;:41;233857:449::o;236713:194::-;236798:14;;-1:-1:-1;;;;;236798:14:0;236776:123;;;;-1:-1:-1;;;236776:123:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;236713:194::o;236915:239::-;237015:29;;-1:-1:-1;;;;;237015:29:0;236993:153;;;;-1:-1:-1;;;236993:153:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;237162:212;237253:20;;-1:-1:-1;;;;;237253:20:0;237231:135;;;;-1:-1:-1;;;237231:135:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;235842:198;235962:20;;236001:31;;;-1:-1:-1;;;236001:31:0;;-1:-1:-1;;;;;236001:31:0;;;;;;;;;235901:4;;235962:20;;;;;;;236001:26;;:31;;;;;;;;;;;;;;235962:20;236001:31;;;5:2:-1;;;;30:1;27;20:12;5:2;236001:31:0;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;236001:31:0;;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;236001:31:0;;235842:198;-1:-1:-1;;;235842:198:0:o;235213:418::-;235338:4;;235360:219;-1:-1:-1;;;;;235384:32:0;;;;;;:14;:32;;;;;:43;;:50;235380:54;;235360:219;;;-1:-1:-1;;;;;235460:32:0;;;;;;;:14;:32;;;;;:43;;:46;;:60;;;;235504:1;;235460:46;;;;;;;;;;;;;;;;-1:-1:-1;;;;;235460:46:0;:60;235456:112;;;235548:4;235541:11;;;;;235456:112;235436:3;;235360:219;;;-1:-1:-1;235618:5:0;;235213:418;-1:-1:-1;;;235213:418:0:o;7231:181::-;7289:7;7321:5;;;7345:6;;;;7337:46;;;;;-1:-1:-1;;;7337:46:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;7403:1;7231:181;-1:-1:-1;;;7231:181:0:o;226624:707::-;-1:-1:-1;;;;;226935:32:0;;;;;;;:14;:32;;;;;;;;:90;;;227120:24;;;;;:12;:24;;;;;:42;;;;;;;;:75;;;227263:60;227133:10;227302:20;227263:26;:60::i;:::-;226624:707;;;;;:::o;8603:471::-;8661:7;8906:6;8902:47;;-1:-1:-1;8936:1:0;8929:8;;8902:47;8973:5;;;8977:1;8973;:5;:1;8997:5;;;;;:10;8989:56;;;;-1:-1:-1;;;8989:56:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;9542:132;9600:7;9627:39;9631:1;9634;9627:39;;;;;;;;;;;;;;;;;:3;:39::i;7687:136::-;7745:7;7772:43;7776:1;7779;7772:43;;;;;;;;;;;;;;;;;:3;:43::i;228583:147::-;-1:-1:-1;;;;;228681:31:0;;;;;;;:19;:31;;;;;:41;228583:147::o;228955:232::-;-1:-1:-1;;;;;229105:32:0;;;;;;;:14;:32;;;;;:51;;:74;228955:232::o;227461:150::-;227544:59;227574:10;227594:1;227598;227601;227544:29;:59::i;236255:338::-;-1:-1:-1;;;;;236380:30:0;;236335:4;236380:30;;;:18;:30;;;;;;:48;;;:53;;;;236379:116;;-1:-1:-1;;;;;;236452:30:0;;;;;;:18;:30;;;;;;:37;;;:42;;236379:116;:195;;;;-1:-1:-1;;;;;;;236513:30:0;;;236571:1;236513:30;;;:18;:30;;;;;;:46;;:60;;;236255:338::o;227948:442::-;228217:165;;;;;;;;-1:-1:-1;;;;;228217:165:0;;;;;;;;;;;;;;;;;;228184:30;;;-1:-1:-1;228184:30:0;;;;;;;;;:198;;;;-1:-1:-1;;;;;;228184:198:0;;;;;;;;;;-1:-1:-1;228184:198:0;;;;;;;;;227948:442::o;229195:621::-;229306:9;229301:508;-1:-1:-1;;;;;229325:32:0;;;;;;:14;:32;;;;;:43;;:50;229321:54;;229301:508;;;-1:-1:-1;;;;;229401:32:0;;;;;;;:14;:32;;;;;:43;;:46;;:60;;;;229445:1;;229401:46;;;;;;;;;;;;;;;;-1:-1:-1;;;;;229401:46:0;:60;229397:401;;;-1:-1:-1;;;;;229588:32:0;;;;;;:14;:32;;;;;:43;;229632:50;;-1:-1:-1;;229632:54:0;;;229588:99;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;229539:32:0;;;;;:14;:32;;;;;;;:43;;:46;;229588:99;;;;;229539:43;229583:1;;229539:46;;;;;;;;;;;;;;;;;;:148;;-1:-1:-1;;;;;;229539:148:0;-1:-1:-1;;;;;229539:148:0;;;;;;229706:32;;;;;:14;:32;;;;;;:43;;:52;;;;;-1:-1:-1;;229706:52:0;;;:::i;:::-;;229777:5;;229397:401;229377:3;;229301:508;;;;229195:621;;:::o;224024:2113::-;224148:29;224188:31;224230:24;224265:20;224296:19;224417:21;224443:9;-1:-1:-1;;;;;224443:40:0;;224484:16;224443:58;;;;;;;;;;;;;-1:-1:-1;;;;;224443:58:0;-1:-1:-1;;;;;224443:58:0;;;;;;;;;;;;;;;;;;;8:9:-1;5:2;;;30:1;27;20:12;5:2;224443:58:0;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;224443:58:0;;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;224443:58:0;-1:-1:-1;;;;;224556:32:0;;224512:26;224556:32;;;:14;224443:58;224556:32;224443:58;224556:32;;:51;;;224443:58;;-1:-1:-1;224512:26:0;224556:70;;224443:58;224556:70;:55;:70;:::i;:::-;224836:20;;224822:123;;;-1:-1:-1;;;224822:123:0;;-1:-1:-1;;;;;224822:123:0;;;;;;;;;;;;;;;224512:125;;-1:-1:-1;224798:21:0;;224836:20;;;;;224822:48;;:123;;;;;;;;;;;;;;224798:21;224836:20;224822:123;;;5:2:-1;;;;30:1;27;20:12;5:2;224822:123:0;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;224822:123:0;;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;224822:123:0;225046:14;;225038:56;;;-1:-1:-1;;;225038:56:0;;-1:-1:-1;;;;;225038:56:0;;;;;;;;;224822:123;;-1:-1:-1;225046:14:0;;;225038:38;;:56;;;;;224822:123;;225038:56;;;;;;;;225046:14;225038:56;;;5:2:-1;;;;30:1;27;20:12;5:2;225038:56:0;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;225038:56:0;;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;225038:56:0;225248:53;;;-1:-1:-1;;;225248:53:0;;-1:-1:-1;;;;;225248:53:0;;;;;;;;;225038:56;;-1:-1:-1;225248:35:0;;;;;;:53;;;;;;;;;;;;;;;:35;:53;;;5:2:-1;;;;30:1;27;20:12;5:2;225248:53:0;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;225248:53:0;;;;;;;13:3:-1;8;5:12;2:2;;;30:1;27;20:12;2:2;-1:-1;225248:53:0;;;;;;;-1:-1:-1;;;;;225514:32:0;;225434:34;225514:32;;;:14;:32;;;225248:53;225514:32;;:52;225248:53;;-1:-1:-1;225248:53:0;-1:-1:-1;225486:81:0;;225248:53;;225486:81;:27;:81;:::i;:::-;225434:144;-1:-1:-1;225610:50:0;225434:144;225641:18;225610:50;:30;:50;:::i;:::-;225591:69;-1:-1:-1;225712:53:0;:21;225738:26;225712:53;:25;:53;:::i;:::-;225695:13;:70;225673:159;;;;-1:-1:-1;;;225673:159:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;225917:21;225903:12;225885:16;-1:-1:-1;;;;;225879:60:0;;;;;;;;;;;-1:-1:-1;226079:13:0;-1:-1:-1;;;224024:2113:0;;;;;;;;:::o;230421:2762::-;230643:35;;230846:2281;-1:-1:-1;;;;;230870:19:0;;;;;;:14;:19;;;;;:30;;:37;230866:41;;230846:2281;;;-1:-1:-1;;;;;230949:19:0;;230929:17;230949:19;;;:14;:19;;;;;:30;;:33;;230980:1;;230949:33;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;230949:33:0;;;231025:23;;;:12;:23;;;;;;:28;;;;;;;;;;;;;231119:29;;;;;;;;;:45;230949:33;;-1:-1:-1;231025:28:0;;231119:45;;:52;231115:172;;;-1:-1:-1;;;;;231234:29:0;;;;;;:18;:29;;;;;;:36;;;231212:59;;:17;;:59;:21;:59;:::i;:::-;231192:79;;231115:172;231396:27;231426:91;231499:17;231443:36;:17;231465:13;231443:36;:21;:36;:::i;231426:91::-;231396:121;-1:-1:-1;232180:21:0;232204:167;232317:39;:17;232339:16;232317:39;:21;:39;:::i;:::-;232223:56;232266:12;232224:36;:17;232246:13;232224:36;:21;:36;:::i;:::-;232223:42;:56;:42;:56;:::i;232204:167::-;232180:191;-1:-1:-1;232649:72:0;232682:38;:19;232180:191;232682:38;:23;:38;:::i;:::-;232649:12;:23;232662:9;-1:-1:-1;;;;;232649:23:0;-1:-1:-1;;;;;232649:23:0;;;;;;;;;;;;:28;232673:3;-1:-1:-1;;;;;232649:28:0;-1:-1:-1;;;;;232649:28:0;;;;;;;;;;;;;:32;;:72;;;;:::i;:::-;232599:12;:23;232612:9;-1:-1:-1;;;;;232599:23:0;-1:-1:-1;;;;;232599:23:0;;;;;;;;;;;;:28;232623:3;-1:-1:-1;;;;;232599:28:0;-1:-1:-1;;;;;232599:28:0;;;;;;;;;;;;:137;;;;232801:162;232846:9;232874:74;232909:38;232933:13;232909:19;:23;;:38;;;;:::i;:::-;-1:-1:-1;;;;;232874:30:0;;;;;;:19;:30;;;;;;;:74;:34;:74;:::i;232801:162::-;233029:71;233061:38;:19;233085:13;233061:38;:23;:38;:::i;:::-;233029:27;;:71;:31;:71;:::i;:::-;232980:135;-1:-1:-1;;230909:3:0;;;;;-1:-1:-1;230846:2281:0;;-1:-1:-1;;230846:2281:0;;-1:-1:-1;230421:2762:0;;;;;;;:::o;10204:345::-;10290:7;10392:12;10385:5;10377:28;;;;-1:-1:-1;;;10377:28:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;27:10:-1;;8:100;;90:11;;;84:18;71:11;;;64:39;52:2;45:10;8:100;;10377:28:0;;10416:9;10432:1;10428;:5;;;;;;;10204:345;-1:-1:-1;;;;;10204:345:0:o;8160:192::-;8246:7;8282:12;8274:6;;;;8266:29;;;;-1:-1:-1;;;8266:29:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;27:10:-1;;8:100;;90:11;;;84:18;71:11;;;64:39;52:2;45:10;8:100;;8266:29:0;-1:-1:-1;;;8318:5:0;;;8160:192::o;186974:50403::-;;;;;;;;;;-1:-1:-1;;;;;186974:50403:0;;;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

Swarm Source

bzzr://adf86fa6a1d687fc958fb7358f740180d41b29edab267cda0b51ec6234ee7bb0

Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
Loading...
Loading
[ Download: CSV Export  ]

A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.