// Contract: HomeWorkDeployer
// Target function: function setReverseLookup(bytes32 key) external

   * the initial controller set to the address corresponding to the first twenty
   * bytes of the key), and there must not be a contract currently deployed at
   * the home address. These conditions can be checked by calling
   * `getHomeAddressInformation` and `isDeployable` with the same key.
   * @param key bytes32 The unique value used to derive the home address.
   * @param initializationCode bytes The contract creation code that will be
   * used to deploy the contract to the home address.
   * @return The home address of the deployed contract.
   * @dev In order to deploy the contract to the home address, a new contract
   * will be deployed with runtime code set to the initialization code of the
   * contract that will be deployed to the home address. Then, metamorphic
   * initialization code will retrieve that initialization code and use it to
   * set up and deploy the desired contract to the home address. Bear in mind
   * that the deployed contract will interpret msg.sender as the address of THIS
   * contract, and not the address of the submitter - if the constructor of the
   * deployed contract uses msg.sender to set up ownership or other variables,
   * you must modify it to accept a constructor argument with the appropriate
   * address, or alternately to hard-code the intended address. Also, if your
   * contract DOES have constructor arguments, remember to include them as
   * ABI-encoded arguments at the end of the initialization code, just as you
   * would when performing a standard deploy. You may also want to provide the
   * key to `setReverseLookup` in order to find it again using only the home
   * address to prevent accidentally losing the key.
   */
  function deploy(bytes32 key, bytes calldata initializationCode)
    external
    payable
    onlyEmpty(key)
    onlyControllerDeployer(key)
    returns (address homeAddress, bytes32 runtimeCodeHash)
  {
    // Ensure that initialization code was supplied.
    require(initializationCode.length > 0, _NO_INIT_CODE_SUPPLIED);

    // Deploy the initialization storage contract and set address in storage.
    _initializationRuntimeStorageContract = _deployRuntimeStorageContract(
      initializationCode
    );

    // Use metamorphic initialization code to deploy contract to home address.
    (homeAddress, runtimeCodeHash) = _deployToHomeAddress(key);
  }

  /**
   * @notice Mint an ERC721 token to the supplied owner that can be redeemed in
   * order to gain control of a home address corresponding to a given key. Two
   * conditions must be met: the submitter must be designated as the controller
   * of the home address (with the initial controller set to the address
   * corresponding to the first 20 bytes of the key), and there must not be a
   * contract currently deployed at the home address. These conditions can be
   * checked by calling `getHomeAddressInformation` and `isDeployable` with the
   * same key.
   * @param key bytes32 The unique value used to derive the home address.
   * @param owner address The account that will be granted ownership of the
   * ERC721 token.
   * @dev In order to mint an ERC721 token, the assocated home address cannot be
   * in use, or else the token will not be able to deploy to the home address.
   * The controller is set to this contract until the token is redeemed, at
   * which point the redeemer designates a new controller for the home address.
   * The key of the home address and the tokenID of the ERC721 token are the
   * same value, but different types (bytes32 vs. uint256).
   */
  function lock(bytes32 key, address owner)
    external
    onlyEmpty(key)
    onlyController(key)
  {
    // Ensure that the specified owner is a valid ERC721 receiver.
    _validateOwner(owner, key);

    // Get the HomeAddress storage struct from the mapping using supplied key.
    HomeAddress storage home = _home[key];

    // Set the exists flag to true and the controller to this contract.
    home.exists = true;
    home.controller = address(this);

    // Emit an event signifying that this contract is now the controller. 
    emit NewController(key, address(this));

    // Mint the ERC721 token to the designated owner.
    _mint(owner, uint256(key));
  }

  /**
   * @notice Burn an ERC721 token to allow the supplied controller to gain the
   * ability to deploy to the home address corresponding to the key matching the
   * burned token. The submitter must be designated as either the owner of the
   * token or as an approved spender.
   * @param tokenId uint256 The ID of the ERC721 token to redeem.
   * @param controller address The account that will be granted control of the
   * home address corresponding to the given token.
   * @dev The controller cannot be designated as the address of this contract,
   * the null address, or the home address (the restriction on setting the home
   * address as the controller is due to the fact that the home address will not
   * be able to deploy to itself, as it needs to be empty before a contract can
   * be deployed to it).
   */
  function redeem(uint256 tokenId, address controller)
    external
    onlyTokenOwnerOrApprovedSpender(tokenId)
  {
    // Convert the token ID to a bytes32 key.
    bytes32 key = bytes32(tokenId);

    // Prevent the controller from being set to prohibited account values.
    _validateController(controller, key);

    // Burn the ERC721 token in question.
    _burn(tokenId);

    // Assign the new controller to the corresponding home address.
    _home[key].controller = controller;

    // Emit an event with the new controller. 
    emit NewController(key, controller);
  }

  /**
   * @notice Transfer control over deployment to the home address corresponding
   * to a given key. The caller must be designated as the current controller of
   * the home address (with the initial controller set to the address
   * corresponding to the first 20 bytes of the key) - This condition can be
   * checked by calling `getHomeAddressInformation` with the same key.
   * @param key bytes32 The unique value used to derive the home address.
   * @param controller address The account that will be granted control of the
   * home address corresponding to the given key.
   * @dev The controller cannot be designated as the address of this contract,
   * the null address, or the home address (the restriction on setting the home
   * address as the controller is due to the fact that the home address will not
   * be able to deploy to itself, as it needs to be empty before a contract can
   * be deployed to it).
   */
  function assignController(bytes32 key, address controller)
    external
    onlyController(key)
  {
    // Prevent the controller from being set to prohibited account values.
    _validateController(controller, key);

    // Assign the new controller to the corresponding home address.
    HomeAddress storage home = _home[key];
    home.exists = true;
    home.controller = controller;

    // Emit an event with the new controller. 
    emit NewController(key, controller);
  }

  /**
   * @notice Transfer control over deployment to the home address corresponding
   * to a given key to the null address, which will prevent it from being
   * deployed to again in the future. The caller must be designated as the
   * current controller of the corresponding home address (with the initial
   * controller set to the address corresponding to the first 20 bytes of the
   * key) - This condition can be checked by calling `getHomeAddressInformation`
   * with the same key.
   * @param key bytes32 The unique value used to derive the home address.
   */
  function relinquishControl(bytes32 key)
    external
    onlyController(key)
  {
    // Assign the null address as the controller of the given key.
    HomeAddress storage home = _home[key];
    home.exists = true;
    home.controller = address(0);

    // Emit an event with the null address as the controller. 
    emit NewController(key, address(0));
  }

  /**
   * @notice Burn an ERC721 token, set a supplied controller, and deploy a new
   * contract with the supplied initialization code to the corresponding home
   * address for the given token. The submitter must be designated as either the
   * owner of the token or as an approved spender.
   * @param tokenId uint256 The ID of the ERC721 token to redeem.
   * @param controller address The account that will be granted control of the
   * home address corresponding to the given token.
   * @param initializationCode bytes The contract creation code that will be
   * used to deploy the contract to the home address.
   * @return The home address and runtime code hash of the deployed contract.
   * @dev In order to deploy the contract to the home address, a new contract
   * will be deployed with runtime code set to the initialization code of the
   * contract that will be deployed to the home address. Then, metamorphic
   * initialization code will retrieve that initialization code and use it to
   * set up and deploy the desired contract to the home address. Bear in mind
   * that the deployed contract will interpret msg.sender as the address of THIS
   * contract, and not the address of the submitter - if the constructor of the
   * deployed contract uses msg.sender to set up ownership or other variables,
   * you must modify it to accept a constructor argument with the appropriate
   * address, or alternately to hard-code the intended address. Also, if your
   * contract DOES have constructor arguments, remember to include them as
   * ABI-encoded arguments at the end of the initialization code, just as you
   * would when performing a standard deploy. You may also want to provide the
   * key to `setReverseLookup` in order to find it again using only the home
   * address to prevent accidentally losing the key. The controller cannot be
   * designated as the address of this contract, the null address, or the home
   * address (the restriction on setting the home address as the controller is
   * due to the fact that the home address will not be able to deploy to itself,
   * as it needs to be empty before a contract can be deployed to it). Also,
   * checks on the contract at the home address being empty or not having the
   * correct controller are unnecessary, as they are performed when minting the
   * token and cannot be altered until the token is redeemed.
   */
  function redeemAndDeploy(
    uint256 tokenId,
    address controller,
    bytes calldata initializationCode
  )
    external
    payable
    onlyTokenOwnerOrApprovedSpender(tokenId)
    returns (address homeAddress, bytes32 runtimeCodeHash)
  {
    // Ensure that initialization code was supplied.
    require(initializationCode.length > 0, _NO_INIT_CODE_SUPPLIED);

    // Convert the token ID to a bytes32 key.
    bytes32 key = bytes32(tokenId);

    // Prevent the controller from being set to prohibited account values.
    _validateController(controller, key);

    // Burn the ERC721 token in question.
    _burn(tokenId);

    // Deploy the initialization storage contract and set address in storage.
    _initializationRuntimeStorageContract = _deployRuntimeStorageContract(
      initializationCode
    );

    // Set provided controller and increment contract deploy count at once.
    HomeAddress storage home = _home[key];
    home.exists = true;
    home.controller = controller;
    home.deploys += 1;

    // Emit an event with the new controller. 
    emit NewController(key, controller);

    // Use metamorphic initialization code to deploy contract to home address.
    (homeAddress, runtimeCodeHash) = _deployToHomeAddress(key);
  }

  /**
   * @notice Derive a new key by concatenating an arbitrary 32-byte salt value
   * and the address of the caller and performing a keccak256 hash. This allows
   * for the creation of keys with additional entropy where desired while also
   * preventing collisions with standard keys. The caller will be set as the
   * controller of the derived key.
   * @param salt bytes32 The desired salt value to use (along with the address
   * of the caller) when deriving the resultant key and corresponding home
   * address.
   * @return The derived key.
   * @dev Home addresses from derived keys will take longer to "mine" or locate,
   * as an additional hash must be performed when computing the corresponding
   * home address for each given salt input. Each caller will derive a different
   * key even if they are supplying the same salt value.
   */
  function deriveKey(bytes32 salt) external returns (bytes32 key) {
    // Derive the key using the supplied salt and the calling address.
    key = _deriveKey(salt, msg.sender);

    // Register key and set caller as controller if it is not yet registered.
    HomeAddress storage home = _home[key];
    if (!home.exists) {
      home.exists = true;
      home.controller = msg.sender;

      // Emit an event with the sender as the new controller. 
      emit NewController(key, msg.sender);
    }
  }

  /**
   * @notice Mint an ERC721 token to the supplied owner that can be redeemed in
   * order to gain control of a home address corresponding to a given derived
   * key. Two conditions must be met: the submitter must be designated as the
   * current controller of the home address, and there must not be a contract
   * currently deployed at the home address. These conditions can be checked by
   * calling `getHomeAddressInformation` and `isDeployable` with the key
   * determined by calling `getDerivedKey`.
   * @param salt bytes32 The salt value that is used to derive the key.
   * @param owner address The account that will be granted ownership of the
   * ERC721 token.
   * @return The derived key.
   * @dev In order to mint an ERC721 token, the assocated home address cannot be
   * in use, or else the token will not be able to deploy to the home address.
   * The controller is set to this contract until the token is redeemed, at
   * which point the redeemer designates a new controller for the home address.
   * The key of the home address and the tokenID of the ERC721 token are the
   * same value, but different types (bytes32 vs. uint256).
   */
  function deriveKeyAndLock(bytes32 salt, address owner)
    external
    returns (bytes32 key)
  {
    // Derive the key using the supplied salt and the calling address.
    key = _deriveKey(salt, msg.sender);

    // Ensure that the specified owner is a valid ERC721 receiver.
    _validateOwner(owner, key);

    // Ensure that a contract is not currently deployed to the home address.
    require(_isNotDeployed(key), _ACCOUNT_EXISTS);

    // Ensure that the caller is the controller of the derived key.
    HomeAddress storage home = _home[key];
    if (home.exists) {
      require(home.controller == msg.sender, _ONLY_CONTROLLER);
    }

    // Set the exists flag to true and the controller to this contract.
    home.exists = true;
    home.controller = address(this);

    // Mint the ERC721 token to the designated owner.
    _mint(owner, uint256(key));

    // Emit an event signifying that this contract is now the controller. 
    emit NewController(key, address(this));
  }

  /**
   * @notice Transfer control over deployment to the home address corresponding
   * to a given derived key. The caller must be designated as the current
   * controller of the home address - This condition can be checked by calling
   * `getHomeAddressInformation` with the key obtained via `getDerivedKey`.
   * @param salt bytes32 The salt value that is used to derive the key.
   * @param controller address The account that will be granted control of the
   * home address corresponding to the given derived key.
   * @return The derived key.
   * @dev The controller cannot be designated as the address of this contract,
   * the null address, or the home address (the restriction on setting the home
   * address as the controller is due to the fact that the home address will not
   * be able to deploy to itself, as it needs to be empty before a contract can
   * be deployed to it).
   */
  function deriveKeyAndAssignController(bytes32 salt, address controller)
    external
    returns (bytes32 key)
  {
    // Derive the key using the supplied salt and the calling address.
    key = _deriveKey(salt, msg.sender);

    // Prevent the controller from being set to prohibited account values.
    _validateController(controller, key);

    // Ensure that the caller is the controller of the derived key.
    HomeAddress storage home = _home[key];
    if (home.exists) {
      require(home.controller == msg.sender, _ONLY_CONTROLLER);
    }

    // Assign the new controller to the corresponding home address.
    home.exists = true;
    home.controller = controller;

    // Emit an event with the new controller. 
    emit NewController(key, controller);
  }

  /**
   * @notice Transfer control over deployment to the home address corresponding
   * to a given derived key to the null address, which will prevent it from
   * being deployed to again in the future. The caller must be designated as the
   * current controller of the home address - This condition can be checked by
   * calling `getHomeAddressInformation` with the key determined by calling
   * `getDerivedKey`.
   * @param salt bytes32 The salt value that is used to derive the key.
   * @return The derived key.
   */
  function deriveKeyAndRelinquishControl(bytes32 salt)
    external
    returns (bytes32 key)
  {
    // Derive the key using the supplied salt and the calling address.
    key = _deriveKey(salt, msg.sender);

    // Ensure that the caller is the controller of the derived key.
    HomeAddress storage home = _home[key];
    if (home.exists) {
      require(home.controller == msg.sender, _ONLY_CONTROLLER);
    }

    // Assign the null address as the controller of the given derived key.
    home.exists = true;
    home.controller = address(0);

    // Emit an event with the null address as the controller. 
    emit NewController(key, address(0));
  }