// Contract: HomeWorkDeployer
// Target function: function safeBatchTransferFrom(
 reverse lookup
   * of a key using only the home address in question. This method may be called
   * by anyone - control of the key is not required.
   * @param key bytes32 The unique value used to derive the home address.
   * @dev This does not set the salt or submitter fields, as those apply only to
   * derived keys (although a derived key may also be set with this method, just
   * without the derived fields).
   */
  function setReverseLookup(bytes32 key) external {
    // Derive home address of given key and set home address and key in mapping.
    _key[_getHomeAddress(key)].key = key;
  }

  /**
   * @notice Record the derived key that corresponds to a given home address by
   * supplying the salt and submitter that were used to derive the key. This
   * facititates reverse lookup of the derivation method of a key using only the
   * home address in question. This method may be called by anyone - control of
   * the derived key is not required.
   * @param salt bytes32 The salt value that is used to derive the key.
   * @param submitter address The account that submits the salt that is used to
   * derive the key.
   */
  function setDerivedReverseLookup(bytes32 salt, address submitter) external {
    // Derive the key using the supplied salt and submitter.
    bytes32 key = _deriveKey(salt, submitter);

    // Derive home address and set it along with all other relevant information.
    _key[_getHomeAddress(key)] = KeyInformation({
      key: key,
      salt: salt,
      submitter: submitter
    });
  }

  /**
   * @notice Deploy a new storage contract with the supplied code as runtime
   * code without deploying a contract to a home address. This can be used to
   * store the contract creation code for use in future deployments of contracts
   * to home addresses.
   * @param codePayload bytes The code to set as the runtime code of the
   * deployed contract.
   * @return The address of the deployed storage contract.
   * @dev Consider placing adequate protections on the storage contract to
   * prevent unwanted callers from modifying or destroying it. Also, if you are
   * placing contract contract creation code into the runtime storage contract,
   * remember to include any constructor parameters as ABI-encoded arguments at
   * the end of the contract creation code, similar to how you would perform a
   * standard deployment.
   */
  function deployRuntimeStorageContract(bytes calldata codePayload)
    external
    returns (address runtimeStorageContract)
  {
    // Ensure that a code payload was supplied.
    require(codePayload.length > 0, "No runtime code payload supplied.");

    // Deploy payload to the runtime storage contract and return the address.
    runtimeStorageContract = _deployRuntimeStorageContract(codePayload);
  }

  /**
   * @notice Deploy a new contract with the initialization code stored in the
   * runtime code at the specified initialization runtime storage contract to
   * the 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 initializationRuntimeStorageContract address The storage contract
   * with runtime code equal to 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 When deploying a contract to a home address via this method, the
   * metamorphic initialization code will retrieve whatever initialization code
   * currently resides at the specified address 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 deployViaExistingRuntimeStorageContract(
    bytes32 key,
    address initializationRuntimeStorageContract
  )
    external
    payable
    onlyEmpty(key)
    onlyControllerDeployer(key)
    returns (address homeAddress, bytes32 runtimeCodeHash)
  {
    // Ensure that the supplied runtime storage contract is not empty.
    _validateRuntimeStorageIsNotEmpty(initializationRuntimeStorageContract);

    // Set initialization runtime storage contract address in contract storage.
    _initializationRuntimeStorageContract = initializationRuntimeStorageContract;

    // Use metamorphic initialization code to deploy contract to home address.
    (homeAddress, runtimeCodeHash) = _deployToHomeAddress(key);
  }

  /**
   * @notice Burn an ERC721 token, set a supplied controller, and deploy a new
   * contract with the initialization code stored in the runtime code at the
   * specified initialization runtime storage contract to the home address
   * corresponding to a given key. 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 initializationRuntimeStorageContract address The storage contract
   * with runtime code equal to 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 When deploying a contract to a home address via this method, the
   * metamorphic initialization code will retrieve whatever initialization code
   * currently resides at the specified address 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 redeemAndDeployViaExistingRuntimeStorageContract(
    uint256 tokenId,
    address controller,
    address initializationRuntimeStorageContract
  )
    external
    payable
    onlyTokenOwnerOrApprovedSpender(tokenId)
    returns (address homeAddress, bytes32 runtimeCodeHash)
  {
    // Ensure that the supplied runtime storage contract is not empty.
    _validateRuntimeStorageIsNotEmpty(initializationRuntimeStorageContract);

    // 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);

    // Set initialization runtime storage contract address in contract storage.
    _initializationRuntimeStorageContract = initializationRuntimeStorageContract;

    // 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 Deploy a new contract with the desired initialization code to the
   * home address corresponding to a given derived key. Two conditions must be
   * met: the submitter must be designated as the 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 obtained by
   * calling `getDerivedKey`.
   * @param salt bytes32 The salt value that is used to derive the key.
   * @param initializationCode bytes The contract creation code that will be
   * used to deploy the contract to the home address.
   * @return The home address, derived key, 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 want to provide the salt
   * and submitter to `setDerivedReverseLookup` in order to find the salt,
   * submitter, and derived key using only the home address to prevent
   * accidentally losing them.
   */
  function deriveKeyAndDeploy(bytes32 salt, bytes calldata initializationCode)
    external
    payable
    returns (address homeAddress, bytes32 key, bytes32 runtimeCodeHash)
  {
    // Ensure that initialization code was supplied.
    require(initializationCode.length > 0, _NO_INIT_CODE_SUPPLIED);

    // Derive key and prepare to deploy using supplied salt and calling address.
    key = _deriveKeyAndPrepareToDeploy(salt);

    // 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 Deploy a new contract with the initialization code stored in the
   * runtime code at the specified initialization runtime storage contract to
   * the home address corresponding to a given derived key. Two conditions must
   * be met: the submitter must be designated as the 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 obtained by
   * calling `getDerivedKey`.
   * @param salt bytes32 The salt value that is used to derive the key.
   * @param initializationRuntimeStorageContract address The storage contract
   * with runtime code equal to the contract creation code that will be used to
   * deploy the contract to the home address.
   * @return The home address, derived key, and runtime code hash of the
   * deployed contract.
   * @dev When deploying a contract to a home address via this method, the
   * metamorphic initialization code will retrieve whatever initialization code
   * currently resides at the specified address 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 want to provide the salt and
   * submitter to `setDerivedReverseLookup` in order to find the salt,
   * submitter, and derived key using only the home address to prevent
   * accidentally losing them.
   */
  function deriveKeyAndDeployViaExistingRuntimeStorageContract(
    bytes32 salt,
    address initializationRuntimeStorageContract
  )
    external
    payable
    returns (address homeAddress, bytes32 key, bytes32 runtimeCodeHash)
  {
    // Ensure that the supplied runtime storage contract is not empty.
    _validateRuntimeStorageIsNotEmpty(initializationRuntimeStorageContract);

    // Derive key and prepare to deploy using supplied salt and calling address.
    key = _deriveKeyAndPrepareToDeploy(salt);

    // Set the initialization runtime storage contract in contract storage.
    _initializationRuntimeStorageContract = initializationRuntimeStorageContract;

    // Use metamorphic initialization code to deploy contract to home address.
    (homeAddress, runtimeCodeHash) = _deployToHomeAddress(key);
  }

  /**
   * @notice Mint multiple ERC721 tokens, designated by their keys, to the
   * specified owner. Keys that aren't controlled, or that point to home
   * addresses that are currently deployed, will be skipped.
   * @param owner address The account that will be granted ownership of the
   * ERC721 tokens.
   * @param keys bytes32[] An array of values used to derive each home address.
   * @dev If you plan to use this method regularly or want to keep gas costs to
   * an absolute minimum, and are willing to go without standard ABI encoding,
   * see `batchLock_63efZf` for a more efficient (and unforgiving)
   * implementation. For batch token minting with *derived* keys, see
   * `deriveKeysAndBatchLock`.
   */
  function batchLock(address owner, bytes32[] calldata keys) external {
    // Track each key in the array of keys.
    bytes32 key;

    // Ensure that the specified owner is a valid ERC721 receiver.
    if (keys.length > 0) {
      _validateOwner(owner, keys[0]);
    }

    // Iterate through each provided key argument.
    for (uint256 i; i < keys.length; i++) {
      key = keys[i];

      // Skip if the key currently has a contract deployed to its home address.
      if (!_isNotDeployed(key)) {
        continue;
      }

      // Skip if the caller is not the controller.
      if (_getController(key) != msg.sender) {
        continue;
      }

      // Set the exists flag to true and the controller to this contract.
      HomeAddress storage home = _home[key];
      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 Mint multiple ERC721 tokens, designated by salts that are hashed
   * with the caller's address to derive each key, to the specified owner.
   * Derived keys that aren't controlled, or that point to home addresses that
   * are currently deployed, will be skipped.
   * @param owner address The account that will be granted ownership of the
   * ERC721 tokens.
   * @param salts bytes32[] An array of values used to derive each key and
   * corresponding home address.
   * @dev See `batchLock` for batch token minting with standard, non-derived
   * keys.
   */
  function deriveKeysAndBatchLock(address owner, bytes32[] calldata salts)
    external
  {
    // Track each key derived from the array of salts.
    bytes32 key;

    // Ensure that the specified owner is a valid ERC721 receiver.
    if (salts.length > 0) {
      _validateOwner(owner, _deriveKey(salts[0], msg.sender));
    }

    // Iterate through each provided salt argument.
    for (uint256 i; i < salts.length; i++) {
      // Derive the key using the supplied salt and the calling address.
      key = _deriveKey(salts[i], msg.sender);

      // Skip if the key currently has a contract deployed to its home address.
      if (!_isNotDeployed(key)) {
        continue;
      }

      // Skip if the caller is not the controller.
      HomeAddress storage home = _home[key];
      if (home.exists && home.controller != msg.sender) {
        continue;
      }

      // 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));
    }
  }