// Contract: HomeWorkDeployer
// Target function: function _toChecksummedAsciiString(address account)
 which executes the
   * initialization code stored there and returns the runtime code (or reverts).
   * Then, the runtime code returned by the delegatecall is returned, and since
   * we are still in the initialization context, it will be set as the runtime
   * code of the metamorphic contract. The 32-byte metamorphic initialization
   * code is as follows:
   *
   * 0x5859385958601c335a585952fa1582838382515af43d3d93833e601e57fd5bf3
   *
   * 00  58  PC               [0]
   * 01  59  MSIZE            [0, 0]
   * 02  38  CODESIZE         [0, 0, codesize -> 32]
   * returndatac03  59  MSIZE            [0, 0, 32, 0]
   * 04  58  PC               [0, 0, 32, 0, 4]
   * 05  60  PUSH1 0x1c       [0, 0, 32, 0, 4, 28]
   * 07  33  CALLER           [0, 0, 32, 0, 4, 28, caller]
   * 08  5a  GAS              [0, 0, 32, 0, 4, 28, caller, gas]
   * 09  58  PC               [0, 0, 32, 0, 4, 28, caller, gas, 9 -> selector]
   * 10  59  MSIZE            [0, 0, 32, 0, 4, 28, caller, gas, selector, 0]
   * 11  52  MSTORE           [0, 0, 32, 0, 4, 28, caller, gas] <selector>
   * 12  fa  STATICCALL       [0, 0, 1 => success] <init_in_runtime_address>
   * 13  15  ISZERO           [0, 0, 0]
   * 14  82  DUP3             [0, 0, 0, 0]
   * 15  83  DUP4             [0, 0, 0, 0, 0]
   * 16  83  DUP4             [0, 0, 0, 0, 0, 0]
   * 17  82  DUP3             [0, 0, 0, 0, 0, 0, 0]
   * 18  51  MLOAD            [0, 0, 0, 0, 0, 0, init_in_runtime_address]
   * 19  5a  GAS              [0, 0, 0, 0, 0, 0, init_in_runtime_address, gas]
   * 20  f4  DELEGATECALL     [0, 0, 1 => success] {runtime_code}
   * 21  3d  RETURNDATASIZE   [0, 0, 1 => success, size]
   * 22  3d  RETURNDATASIZE   [0, 0, 1 => success, size, size]
   * 23  93  SWAP4            [size, 0, 1 => success, size, 0]
   * 24  83  DUP4             [size, 0, 1 => success, size, 0, 0]
   * 25  3e  RETURNDATACOPY   [size, 0, 1 => success] <runtime_code>
   * 26  60  PUSH1 0x1e       [size, 0, 1 => success, 30]
   * 28  57  JUMPI            [size, 0]
   * 29  fd  REVERT           [] *runtime_code*
   * 30  5b  JUMPDEST         [size, 0]
   * 31  f3  RETURN           []
   */
  function _deployToHomeAddress(bytes32 key)
    internal
    returns (address homeAddress, bytes32 runtimeCodeHash)
  {    
    assembly {
      // Write the 32-byte metamorphic initialization code to scratch space.
      mstore(
        0,
        0x5859385958601c335a585952fa1582838382515af43d3d93833e601e57fd5bf3
      )

      // Call `CREATE2` using above init code with the supplied key as the salt.
      homeAddress := create2(callvalue, 0, 32, key)

      // Pass along revert message if the contract did not deploy successfully.
      if iszero(homeAddress) {
        returndatacopy(0, 0, returndatasize)
        revert(0, returndatasize)
      }

      // Get the runtime hash of the deployed contract.
      runtimeCodeHash := extcodehash(homeAddress)
    }

    // Clear the address of the runtime storage contract from storage.
    delete _initializationRuntimeStorageContract;

    // Emit an event with home address, key, and runtime hash of new contract.
    emit NewResident(homeAddress, key, runtimeCodeHash);
  }

  /**
   * @notice Internal function for deriving a key given a particular salt and
   * caller and for performing verifications of, and modifications to, the
   * information set on that key.
   * @param salt bytes32 The value used to derive the key.
   * @return The derived key.
   */
  function _deriveKeyAndPrepareToDeploy(bytes32 salt)
    internal
    returns (bytes32 key)
  {
    // Derive the key using the supplied salt and the calling address.
    key = _deriveKey(salt, msg.sender);

    // Ensure that a contract is not currently deployed to the home address.
    require(_isNotDeployed(key), _ACCOUNT_EXISTS);

    // Set appropriate controller and increment contract deploy count at once.
    HomeAddress storage home = _home[key];
    if (!home.exists) {
      home.exists = true;
      home.controller = msg.sender;
      home.deploys += 1;

      // Emit an event signifying that this contract is now the controller. 
      emit NewController(key, msg.sender);
    
    } else {
      home.deploys += 1;
    }

    // Ensure that the caller is the designated controller before proceeding.
    require(home.controller == msg.sender, _ONLY_CONTROLLER);
  }

  /**
   * @notice Internal function for verifying that an owner that cannot accept
   * ERC721 tokens has not been supplied.
   * @param owner address The specified owner.
   * @param key bytes32 The unique value used to derive the home address.
   */
  function _validateOwner(address owner, bytes32 key) internal {
    // Ensure that the specified owner is a valid ERC721 receiver.
    require(
      _checkOnERC721Received(address(0), owner, uint256(key), bytes("")),
      "Owner must be an EOA or a contract that implements `onERC721Received`."
    );
  }

  /**
   * @notice Internal "view" function for determining if a contract currently
   * exists at a given home address corresponding to a particular key.
   * @param key bytes32 The unique value used to derive the home address.
   * @return A boolean signifying whether the home address has a contract
   * deployed or not.
   */
  function _isNotDeployed(bytes32 key)
    internal
    /* view */
    returns (bool notDeployed)
  {
    // Derive the home address using the supplied key.
    address homeAddress = _getHomeAddress(key);

    // Check whether account at home address is non-existent using EXTCODEHASH.
    bytes32 hash;
    assembly { hash := extcodehash(homeAddress) }

    // Account does not exist, and contract is not deployed, if hash equals 0.
    if (hash == bytes32(0)) {
      return true;
    }

    // Contract is deployed (notDeployed = false) if codesize is greater than 0.
    uint256 size;
    assembly { size := extcodesize(homeAddress) }
    if (size > 0) {
      return false;
    }

    // Declare variable to move current runtime storage from storage to memory.
    address currentStorage;

    // Set runtime storage contract to null address temporarily if necessary.
    if (_initializationRuntimeStorageContract != address(0)) {
      // Place the current runtime storage contract address in memory.
      currentStorage = _initializationRuntimeStorageContract;
      
      // Remove the existing runtime storage contract address from storage.
      delete _initializationRuntimeStorageContract;
    }

    // Set gas to use when performing dry-run deployment (future-proof a bit).
    uint256 checkGas = 27000 + (block.gaslimit / 1000);
    
    // As a last resort, deploy a contract to the address and revert on success.
    (bool contractExists, bytes memory code) = address(this).call.gas(checkGas)(
      abi.encodeWithSelector(this.staticCreate2Check.selector, key)
    );

    // Place runtime storage contract back in storage if necessary.
    if (currentStorage != address(0)) {
      _initializationRuntimeStorageContract = currentStorage;
    }

    // Check revert string to ensure failure is due to successful deployment.
    bytes32 revertMessage;
    assembly { revertMessage := mload(add(code, 32)) }

    // Contract is not deployed if `staticCreate2Check` reverted with message.
    notDeployed = !contractExists && revertMessage == _HOME_INIT_CODE;
  }

  /**
   * @notice Internal view function for verifying that a restricted controller
   * has not been supplied.
   * @param controller address The specified controller.
   * @param key bytes32 The unique value used to derive the home address.
   */
  function _validateController(address controller, bytes32 key) internal view {
    // Prevent the controller from being set to prohibited account values.
    require(
      controller != address(0),
      "The null address may not be set as the controller using this function."
    );
    require(
      controller != address(this),
      "This contract may not be set as the controller using this function."
    );
    require(
      controller != _getHomeAddress(key),
      "Home addresses cannot be set as the controller of themselves."
    );
  }

  /**
   * @notice Internal view function for verifying that a supplied runtime
   * storage contract is not empty.
   * @param target address The runtime storage contract.
   */
  function _validateRuntimeStorageIsNotEmpty(address target) internal view {
    // Ensure that the runtime storage contract is not empty.
    require(
      target.isContract(),
      "No runtime code found at the supplied runtime storage address."
    );
  }

  /**
   * @notice Internal view function for retrieving the controller of a home
   * address corresponding to a particular key.
   * @param key bytes32 The unique value used to derive the home address.
   * @return The controller of the home address corresponding to the supplied
   * key.
   */
  function _getController(bytes32 key)
    internal
    view
    returns (address controller)
  {
    // Get controller from mapping, defaulting to first 20 bytes of the key.
    HomeAddress memory home = _home[key];
    if (!home.exists) {
      controller = address(bytes20(key));
    } else {
      controller = home.controller;
    }
  }

  /**
   * @notice Internal view function for getting the runtime code at the tokenURI
   * data storage address.
   * @return The runtime code at the tokenURI storage address.
   */
  function _getTokenURIStorageRuntime()
    internal
    view
    returns (bytes memory runtime)
  {
    // Bring the tokenURI storage address into memory for use in assembly block.
    address target = _URI_END_SEGMENT_STORAGE;
    
    assembly {
      // Retrieve the size of the external code.
      let size := extcodesize(target)
      
      // Allocate output byte array.
      runtime := mload(0x40)
      
      // Set new "memory end" including padding.
      mstore(0x40, add(runtime, and(add(size, 0x3f), not(0x1f))))
      
      // Store length in memory.
      mstore(runtime, size)
      
      // Get the code using extcodecopy.
      extcodecopy(target, add(runtime, 0x20), 0, size)
    }
  }

  /**
   * @notice Internal pure function for calculating a home address given a
   * particular key.
   * @param key bytes32 The unique value used to derive the home address.
   * @return The home address corresponding to the supplied key.
   */
  function _getHomeAddress(bytes32 key)
    internal
    pure
    returns (address homeAddress)
  {
    // Determine the home address by replicating CREATE2 logic.
    homeAddress = address(
      uint160(                       // Downcast to match the address type.
        uint256(                     // Cast to uint to truncate upper digits.
          keccak256(                 // Compute CREATE2 hash using 4 inputs.
            abi.encodePacked(        // Pack all inputs to the hash together.
              _FF_AND_THIS_CONTRACT, // This contract will be the caller.
              key,                   // Pass in the supplied key as the salt.
              _HOME_INIT_CODE_HASH   // The metamorphic init code hash.
            )
          )
        )
      )
    );
  }

  /**
   * @notice Internal pure function for deriving a key given a particular salt
   * and caller.
   * @param salt bytes32 The value used to derive the key.
   * @param submitter address The submitter of the salt used to derive the key.
   * @return The derived key.
   */
  function _deriveKey(bytes32 salt, address submitter)
    internal
    pure
    returns (bytes32 key)
  {
    // Set the key as the keccak256 hash of the salt and submitter.
    key = keccak256(abi.encodePacked(salt, submitter));
  }

  /**
   * @notice Internal pure function for converting the bytes representation of
   * an address to an ASCII string. This function is derived from the function
   * at https://ethereum.stackexchange.com/a/56499/48410
   * @param data bytes20 The account address to be converted.
   * @return The account string in ASCII format. Note that leading "0x" is not
   * included.
   */
  function _toAsciiString(bytes20 data)
    internal
    pure
    returns (string memory asciiString)
  {
    // Create an in-memory fixed-size bytes array.
    bytes memory asciiBytes = new bytes(40);

    // Declare variable types.
    uint8 oneByte;
    uint8 leftNibble;
    uint8 rightNibble;

    // Iterate over bytes, processing left and right nibble in each iteration.
    for (uint256 i = 0; i < data.length; i++) {
      // locate the byte and extract each nibble.
      oneByte = uint8(uint160(data) / (2 ** (8 * (19 - i))));
      leftNibble = oneByte / 16;
      rightNibble = oneByte - 16 * leftNibble;

      // To convert to ascii characters, add 48 to 0-9 and 87 to a-f.
      asciiBytes[2 * i] = byte(leftNibble + (leftNibble < 10 ? 48 : 87));
      asciiBytes[2 * i + 1] = byte(rightNibble + (rightNibble < 10 ? 48 : 87));
    }

    asciiString = string(asciiBytes);
  }

  /**
   * @notice Internal pure function for getting a fixed-size array of whether or
   * not each character in an account will be capitalized in the checksum.
   * @param account address The account to get the checksum capitalization
   * information for.
   * @return A fixed-size array of booleans that signify if each character or
   * "nibble" of the hex encoding of the address will be capitalized by the
   * checksum.
   */
  function _getChecksumCapitalizedCharacters(address account)
    internal
    pure
    returns (bool[40] memory characterIsCapitalized)
  {
    // Convert the address to bytes.
    bytes20 addressBytes = bytes20(account);

    // Hash the address (used to calculate checksum).
    bytes32 hash = keccak256(abi.encodePacked(_toAsciiString(addressBytes)));

    // Declare variable types.
    uint8 leftNibbleAddress;
    uint8 rightNibbleAddress;
    uint8 leftNibbleHash;
    uint8 rightNibbleHash;

    // Iterate over bytes, processing left and right nibble in each iteration.
    for (uint256 i; i < addressBytes.length; i++) {
      // locate the byte and extract each nibble for the address and the hash.
      rightNibbleAddress = uint8(addressBytes[i]) % 16;
      leftNibbleAddress = (uint8(addressBytes[i]) - rightNibbleAddress) / 16;
      rightNibbleHash = uint8(hash[i]) % 16;
      leftNibbleHash = (uint8(hash[i]) - rightNibbleHash) / 16;

      // Set the capitalization flags based on the characters and the checksums.
      characterIsCapitalized[2 * i] = (
        leftNibbleAddress > 9 &&
        leftNibbleHash > 7
      );
      characterIsCapitalized[2 * i + 1] = (
        rightNibbleAddress > 9 &&
        rightNibbleHash > 7
      );
    }
  }