From c656b70df60c6402d64040d5f6ca47f87aac0ef5 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Thu, 20 Jun 2019 13:49:36 +0300 Subject: [PATCH 01/15] fix large contract verification --- .../smart_contract/solidity/code_compiler.ex | 10 +- apps/explorer/mix.exs | 1 + apps/explorer/priv/compile_solc.js | 5 +- .../solidity/code_compiler_test.exs | 14 + .../smart_contract/large_smart_contract.sol | 3874 +++++++++++++++++ mix.lock | 1 + 6 files changed, 3903 insertions(+), 2 deletions(-) create mode 100644 apps/explorer/test/support/fixture/smart_contract/large_smart_contract.sol diff --git a/apps/explorer/lib/explorer/smart_contract/solidity/code_compiler.ex b/apps/explorer/lib/explorer/smart_contract/solidity/code_compiler.ex index 3e41bb08fa..14262e7fb9 100644 --- a/apps/explorer/lib/explorer/smart_contract/solidity/code_compiler.ex +++ b/apps/explorer/lib/explorer/smart_contract/solidity/code_compiler.ex @@ -91,7 +91,7 @@ defmodule Explorer.SmartContract.Solidity.CodeCompiler do "node", [ Application.app_dir(:explorer, "priv/compile_solc.js"), - code, + create_source_file(code), compiler_version, optimize_value(optimize), optimization_runs, @@ -162,4 +162,12 @@ defmodule Explorer.SmartContract.Solidity.CodeCompiler do defp optimize_value(true), do: "1" defp optimize_value("true"), do: "1" + + defp create_source_file(source) do + {:ok, path} = Briefly.create() + + File.write!(path, source) + + path + end end diff --git a/apps/explorer/mix.exs b/apps/explorer/mix.exs index 09257b9aee..f224471740 100644 --- a/apps/explorer/mix.exs +++ b/apps/explorer/mix.exs @@ -66,6 +66,7 @@ defmodule Explorer.Mixfile do # CSV output for benchee {:benchee_csv, "~> 0.8.0", only: :test}, {:bypass, "~> 1.0", only: :test}, + {:briefly, "~> 0.4", github: "CargoSense/briefly"}, {:comeonin, "~> 4.0"}, {:credo, "1.0.0", only: :test, runtime: false}, # For Absinthe to load data in batches diff --git a/apps/explorer/priv/compile_solc.js b/apps/explorer/priv/compile_solc.js index eea727802e..99048d9ffd 100755 --- a/apps/explorer/priv/compile_solc.js +++ b/apps/explorer/priv/compile_solc.js @@ -1,6 +1,6 @@ #!/usr/bin/env node -var sourceCode = process.argv[2]; +var sourceCodePath = process.argv[2]; var version = process.argv[3]; var optimize = process.argv[4]; var optimizationRuns = parseInt(process.argv[5], 10); @@ -13,6 +13,9 @@ var solc = require('solc') var compilerSnapshot = require(compilerVersionPath); var solc = solc.setupMethods(compilerSnapshot); +var fs = require('fs'); +var sourceCode = fs.readFileSync(sourceCodePath, 'utf8'); + const input = { language: 'Solidity', sources: { diff --git a/apps/explorer/test/explorer/smart_contract/solidity/code_compiler_test.exs b/apps/explorer/test/explorer/smart_contract/solidity/code_compiler_test.exs index be68a03a0e..d1c1d04b45 100644 --- a/apps/explorer/test/explorer/smart_contract/solidity/code_compiler_test.exs +++ b/apps/explorer/test/explorer/smart_contract/solidity/code_compiler_test.exs @@ -268,6 +268,20 @@ defmodule Explorer.SmartContract.Solidity.CodeCompilerTest do assert Enum.any?(abi, fn el -> el["type"] == "constructor" end) end + + test "can compile a large file" do + path = File.cwd!() <> "/test/support/fixture/smart_contract/large_smart_contract.sol" + contract = File.read!(path) + + assert {:ok, %{"abi" => abi}} = + CodeCompiler.run( + name: "HomeWorkDeployer", + compiler_version: "v0.5.9+commit.e560f70d", + code: contract, + evm_version: "constantinople", + optimize: true + ) + end end describe "get_contract_info/1" do diff --git a/apps/explorer/test/support/fixture/smart_contract/large_smart_contract.sol b/apps/explorer/test/support/fixture/smart_contract/large_smart_contract.sol new file mode 100644 index 0000000000..6b147f21d8 --- /dev/null +++ b/apps/explorer/test/support/fixture/smart_contract/large_smart_contract.sol @@ -0,0 +1,3874 @@ +/** + *Submitted for verification at Etherscan.io on 2019-06-05 +*/ + +pragma solidity 0.5.9; // optimization enabled, runs: 10000, evm: constantinople + + +/** + * @title HomeWork Interface (version 1) - EIP165 ID 0xe5399799 + * @author 0age + * @notice Homework is a utility to find, share, and reuse "home" addresses for + * contracts. Anyone can work to find a new home address by searching for keys, + * a 32-byte value with the first 20 bytes equal to the finder's calling address + * (or derived by hashing an arbitrary 32-byte salt and the caller's address), + * and can then deploy any contract they like (even one with a constructor) to + * the address, or mint an ERC721 token that the owner can redeem that will then + * allow them to do the same. Also, if the contract is `SELFDESTRUCT`ed, a new + * contract can be redeployed by the current controller to the same address! + * @dev This contract allows contract addresses to be located ahead of time, and + * for arbitrary bytecode to be deployed (and redeployed if so desired, i.e. + * metamorphic contracts) to the located address by a designated controller. To + * enable this, the contract first deploys an "initialization-code-in-runtime" + * contract, with the creation code of the contract you want to deploy stored in + * RUNTIME code. Then, to deploy the actual contract, it retrieves the address + * of the storage contract and `DELEGATECALL`s into it to execute the init code + * and, if successful, retrieves and returns the contract runtime code. Rather + * than using a located address directly, you can also lock it in the contract + * and mint and ERC721 token for it, which can then be redeemed in order to gain + * control over deployment to the address (note that tokens may not be minted if + * the contract they control currently has a deployed contract at that address). + * Once a contract undergoes metamorphosis, all existing storage will be deleted + * and any existing contract code will be replaced with the deployed contract + * code of the new implementation contract. The mechanisms behind this contract + * are highly experimental - proceed with caution and please share any exploits + * or optimizations you discover. + */ +interface IHomeWork { + // Fires when a contract is deployed or redeployed to a given home address. + event NewResident( + address indexed homeAddress, + bytes32 key, + bytes32 runtimeCodeHash + ); + + // Fires when a new runtime storage contract is deployed. + event NewRuntimeStorageContract( + address runtimeStorageContract, + bytes32 runtimeCodeHash + ); + + // Fires when a controller is changed from the default controller. + event NewController(bytes32 indexed key, address newController); + + // Fires when a new high score is submitted. + event NewHighScore(bytes32 key, address submitter, uint256 score); + + // Track total contract deploys and current controller for each home address. + struct HomeAddress { + bool exists; + address controller; + uint88 deploys; + } + + // Track derivation of key for a given home address based on salt & submitter. + struct KeyInformation { + bytes32 key; + bytes32 salt; + address submitter; + } + + /** + * @notice Deploy a new contract with the desired initialization code 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 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 + returns (address homeAddress, bytes32 runtimeCodeHash); + + /** + * @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; + + /** + * @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; + + /** + * @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; + + /** + * @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; + + /** + * @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 + returns (address homeAddress, bytes32 runtimeCodeHash); + + /** + * @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); + + /** + * @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); + + /** + * @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); + + /** + * @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); + + /** + * @notice Record a key that corresponds to a given home address by supplying + * said key and using it to derive the address. This enables 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; + + /** + * @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; + + /** + * @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); + + /** + * @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 + returns (address homeAddress, bytes32 runtimeCodeHash); + + /** + * @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 + returns (address homeAddress, bytes32 runtimeCodeHash); + + /** + * @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); + + /** + * @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); + + /** + * @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; + + /** + * @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; + + /** + * @notice Efficient version of `batchLock` that uses less gas. The first 20 + * bytes of each key are automatically populated using msg.sender, and the + * remaining key segments are passed in as a packed byte array, using twelve + * bytes per segment, with a function selector of 0x00000000 followed by a + * twenty-byte segment for the desired owner of the minted ERC721 tokens. Note + * that an attempt to lock a key that is not controlled or with its contract + * already deployed will cause the entire batch to revert. Checks on whether + * the owner is a valid ERC721 receiver are also skipped, similar to using + * `transferFrom` instead of `safeTransferFrom`. + */ + function batchLock_63efZf(/* packed owner and key segments */) external; + + /** + * @notice Submit a key to claim the "high score" - the lower the uint160 + * value of the key's home address, the higher the score. The high score + * holder has the exclusive right to recover lost ether and tokens on this + * contract. + * @param key bytes32 The unique value used to derive the home address that + * will determine the resultant score. + * @dev The high score must be claimed by a direct key (one that is submitted + * by setting the first 20 bytes of the key to the address of the submitter) + * and not by a derived key, and is non-transferrable. If you want to help + * people recover their lost tokens, you might consider deploying a contract + * to the high score address (probably a metamorphic one so that you can use + * the home address later) with your contact information. + */ + function claimHighScore(bytes32 key) external; + + /** + * @notice Transfer any ether or ERC20 tokens that have somehow ended up at + * this contract by specifying a token address (set to the null address for + * ether) as well as a recipient address. Only the high score holder can + * recover lost ether and tokens on this contract. + * @param token address The contract address of the ERC20 token to recover, or + * the null address for recovering Ether. + * @param recipient address payable The account where recovered funds should + * be transferred. + * @dev If you are trying to recover funds that were accidentally sent into + * this contract, see if you can contact the holder of the current high score, + * found by calling `getHighScore`. Better yet, try to find a new high score + * yourself! + */ + function recover(IERC20 token, address payable recipient) external; + + /** + * @notice "View" function to determine if a contract can currently be + * deployed to a home address given the corresponding key. A contract is only + * deployable if no account currently exists at the address - any existing + * contract must be destroyed via `SELFDESTRUCT` before a new contract can be + * deployed to a home address. This method does not modify state but is + * inaccessible via staticcall. + * @param key bytes32 The unique value used to derive the home address. + * @return A boolean signifying if a contract can be deployed to the home + * address that corresponds to the provided key. + * @dev This will not detect if a contract is not deployable due control + * having been relinquished on the key. + */ + function isDeployable(bytes32 key) + external + /* view */ + returns (bool deployable); + + /** + * @notice View function to get the current "high score", or the lowest + * uint160 value of a home address of all keys submitted. The high score + * holder has the exclusive right to recover lost ether and tokens on this + * contract. + * @return The current high score holder, their score, and the submitted key. + */ + function getHighScore() + external + view + returns (address holder, uint256 score, bytes32 key); + + /** + * @notice View function to get information on a home address given the + * corresponding key. + * @param key bytes32 The unique value used to derive the home address. + * @return The home address, the current controller of the address, the number + * of times the home address has been deployed to, and the code hash of the + * runtime currently found at the home address, if any. + * @dev There is also an `isDeployable` method for determining if a contract + * can be deployed to the address, but in extreme cases it must actually + * perform a dry-run to determine if the contract is deployable, which means + * that it does not support staticcalls. There is also a convenience method, + * `hasNeverBeenDeployed`, but the information it conveys can be determined + * from this method alone as well. + */ + function getHomeAddressInformation(bytes32 key) + external + view + returns ( + address homeAddress, + address controller, + uint256 deploys, + bytes32 currentRuntimeCodeHash + ); + + /** + * @notice View function to determine if no contract has ever been deployed to + * a home address given the corresponding key. This can be used to ensure that + * a given key or corresponding token is "new" or not. + * @param key bytes32 The unique value used to derive the home address. + * @return A boolean signifying if a contract has never been deployed using + * the supplied key before. + */ + function hasNeverBeenDeployed(bytes32 key) + external + view + returns (bool neverBeenDeployed); + + /** + * @notice View function to search for a known key, salt, and/or submitter + * given a supplied home address. Keys can be controlled directly by an + * address that matches the first 20 bytes of the key, or they can be derived + * from a salt and a submitter - if the key is not a derived key, the salt and + * submitter fields will both have a value of zero. + * @param homeAddress address The home address to check for key information. + * @return The key, salt, and/or submitter used to deploy to the home address, + * assuming they have been submitted to the reverse lookup. + * @dev To populate these values, call `setReverseLookup` for cases where keys + * are used directly or are the only value known, or `setDerivedReverseLookup` + * for cases where keys are derived from a known salt and submitter. + */ + function reverseLookup(address homeAddress) + external + view + returns (bytes32 key, bytes32 salt, address submitter); + + /** + * @notice Pure function to determine the key that is derived from a given + * salt and submitting address. + * @param salt bytes32 The salt value that is used to derive the key. + * @param submitter address The submitter of the salt value used to derive the + * key. + * @return The derived key. + */ + function getDerivedKey(bytes32 salt, address submitter) + external + pure + returns (bytes32 key); + + /** + * @notice Pure function to determine the home address that corresponds to + * a given key. + * @param key bytes32 The unique value used to derive the home address. + * @return The home address. + */ + function getHomeAddress(bytes32 key) + external + pure + returns (address homeAddress); + + /** + * @notice Pure function for retrieving the metamorphic initialization code + * used to deploy arbitrary contracts to home addresses. Provided for easy + * verification and for use in other applications. + * @return The 32-byte metamorphic initialization code. + * @dev This metamorphic init code works via the "metamorphic delegator" + * mechanism, which is explained in greater detail at `_deployToHomeAddress`. + */ + function getMetamorphicDelegatorInitializationCode() + external + pure + returns (bytes32 metamorphicDelegatorInitializationCode); + + /** + * @notice Pure function for retrieving the keccak256 of the metamorphic + * initialization code used to deploy arbitrary contracts to home addresses. + * This is the value that you should use, along with this contract's address + * and a caller address that you control, to mine for an partucular type of + * home address (such as one at a compact or gas-efficient address). + * @return The keccak256 hash of the metamorphic initialization code. + */ + function getMetamorphicDelegatorInitializationCodeHash() + external + pure + returns (bytes32 metamorphicDelegatorInitializationCodeHash); + + /** + * @notice Pure function for retrieving the prelude that will be inserted + * ahead of the code payload in order to deploy a runtime storage contract. + * @return The 11-byte "arbitrary runtime" prelude. + */ + function getArbitraryRuntimeCodePrelude() + external + pure + returns (bytes11 prelude); +} + + +/** + * @title ERC721 Non-Fungible Token Standard basic interface + * @dev see https://eips.ethereum.org/EIPS/eip-721 + */ +interface IERC721 { + event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); + event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); + event ApprovalForAll(address indexed owner, address indexed operator, bool approved); + + function balanceOf(address owner) external view returns (uint256 balance); + function ownerOf(uint256 tokenId) external view returns (address owner); + + function approve(address to, uint256 tokenId) external; + function getApproved(uint256 tokenId) external view returns (address operator); + + function setApprovalForAll(address operator, bool _approved) external; + function isApprovedForAll(address owner, address operator) external view returns (bool); + + function transferFrom(address from, address to, uint256 tokenId) external; + function safeTransferFrom(address from, address to, uint256 tokenId) external; + + function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external; +} + + +/** + * @title ERC-721 Non-Fungible Token Standard, optional enumeration extension + * @dev See https://eips.ethereum.org/EIPS/eip-721 + */ +interface IERC721Enumerable { + function totalSupply() external view returns (uint256); + function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256 tokenId); + function tokenByIndex(uint256 index) external view returns (uint256); +} + + +/** + * @title ERC-721 Non-Fungible Token Standard, optional metadata extension + * @dev See https://eips.ethereum.org/EIPS/eip-721 + */ +interface IERC721Metadata { + function name() external pure returns (string memory); + function symbol() external pure returns (string memory); + function tokenURI(uint256 tokenId) external view returns (string memory); +} + + +/** + * @title ERC721 token receiver interface + * @dev Interface for any contract that wants to support safeTransfers + * from ERC721 asset contracts. + */ +interface IERC721Receiver { + /** + * @notice Handle the receipt of an NFT + * @dev The ERC721 smart contract calls this function on the recipient + * after a `safeTransfer`. This function MUST return the function selector, + * otherwise the caller will revert the transaction. The selector to be + * returned can be obtained as `this.onERC721Received.selector`. This + * function MAY throw to revert and reject the transfer. + * Note: the ERC721 contract address is always the message sender. + * @param operator The address which called `safeTransferFrom` function + * @param from The address which previously owned the token + * @param tokenId The NFT identifier which is being transferred + * @param data Additional data with no specified format + * @return bytes4 `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` + */ + function onERC721Received(address operator, address from, uint256 tokenId, bytes calldata data) + external + returns (bytes4); +} + + +/** + * @title ERC1412 Batch Transfers For Non-Fungible Tokens + * @dev the ERC-165 identifier for this interface is 0x2b89bcaa + */ +interface IERC1412 { + /// @notice Transfers the ownership of multiple NFTs from one address to another address + /// @param _from The current owner of the NFT + /// @param _to The new owner + /// @param _tokenIds The NFTs to transfer + /// @param _data Additional data with no specified format, sent in call to `_to` + function safeBatchTransferFrom(address _from, address _to, uint256[] calldata _tokenIds, bytes calldata _data) external; + + /// @notice Transfers the ownership of multiple NFTs from one address to another address + /// @param _from The current owner of the NFT + /// @param _to The new owner + /// @param _tokenIds The NFTs to transfer + function safeBatchTransferFrom(address _from, address _to, uint256[] calldata _tokenIds) external; +} + + +/** + * @title IERC165 + * @dev https://eips.ethereum.org/EIPS/eip-165 + */ +interface IERC165 { + /** + * @notice Query if a contract implements an interface + * @param interfaceId The interface identifier, as specified in ERC-165 + * @dev Interface identification is specified in ERC-165. This function + * uses less than 30,000 gas. + */ + function supportsInterface(bytes4 interfaceId) external view returns (bool); +} + + +/** + * @title ERC20 interface + * @dev see https://eips.ethereum.org/EIPS/eip-20 + */ +interface IERC20 { + function transfer(address to, uint256 value) external returns (bool); + + function approve(address spender, uint256 value) external returns (bool); + + function transferFrom(address from, address to, uint256 value) external returns (bool); + + function totalSupply() external view returns (uint256); + + function balanceOf(address who) external view returns (uint256); + + function allowance(address owner, address spender) external view returns (uint256); + + event Transfer(address indexed from, address indexed to, uint256 value); + + event Approval(address indexed owner, address indexed spender, uint256 value); +} + + +/** + * @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) { + require(b <= a, "SafeMath: subtraction overflow"); + 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-solidity/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) { + // Solidity only automatically asserts when dividing by 0 + require(b > 0, "SafeMath: division by zero"); + 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) { + require(b != 0, "SafeMath: modulo by zero"); + return a % b; + } +} + + +/** + * Utility library of inline functions on addresses + */ +library Address { + /** + * Returns whether the target address is a contract + * @dev This function will return false if invoked during the constructor of a contract, + * as the code is not actually created until after the constructor finishes. + * @param account address of the account to check + * @return whether the target address is a contract + */ + function isContract(address account) internal view returns (bool) { + uint256 size; + // XXX Currently there is no better way to check if there is a contract in an address + // than to check the size of the code at that address. + // See https://ethereum.stackexchange.com/a/14016/36603 + // for more details about how this works. + // TODO Check this again before the Serenity release, because all addresses will be + // contracts then. + // solhint-disable-next-line no-inline-assembly + assembly { size := extcodesize(account) } + return size > 0; + } +} + + +/** + * @title Counters + * @author Matt Condon (@shrugs) + * @dev Provides counters that can only be incremented or decremented by one. This can be used e.g. to track the number + * of elements in a mapping, issuing ERC721 ids, or counting request ids + * + * Include with `using Counters for Counters.Counter;` + * Since it is not possible to overflow a 256 bit integer with increments of one, `increment` can skip the SafeMath + * overflow check, thereby saving gas. This does assume however correct usage, in that the underlying `_value` is never + * directly accessed. + */ +library Counters { + using SafeMath for uint256; + + struct Counter { + // This variable should never be directly accessed by users of the library: interactions must be restricted to + // the library's function. As of Solidity v0.5.2, this cannot be enforced, though there is a proposal to add + // this feature: see https://github.com/ethereum/solidity/issues/4637 + uint256 _value; // default: 0 + } + + function current(Counter storage counter) internal view returns (uint256) { + return counter._value; + } + + function increment(Counter storage counter) internal { + counter._value += 1; + } + + function decrement(Counter storage counter) internal { + counter._value = counter._value.sub(1); + } +} + + +/** + * @dev Implementation of the `IERC165` interface. + * + * Contracts may inherit from this and call `_registerInterface` to declare + * their support of an interface. + */ +contract ERC165 is IERC165 { + /* + * bytes4(keccak256('supportsInterface(bytes4)')) == 0x01ffc9a7 + */ + bytes4 private constant _INTERFACE_ID_ERC165 = 0x01ffc9a7; + + /** + * @dev Mapping of interface ids to whether or not it's supported. + */ + mapping(bytes4 => bool) private _supportedInterfaces; + + constructor () internal { + // Derived contracts need only register support for their own interfaces, + // we register support for ERC165 itself here + _registerInterface(_INTERFACE_ID_ERC165); + } + + /** + * @dev See `IERC165.supportsInterface`. + * + * Time complexity O(1), guaranteed to always use less than 30 000 gas. + */ + function supportsInterface(bytes4 interfaceId) external view returns (bool) { + return _supportedInterfaces[interfaceId]; + } + + /** + * @dev Registers the contract as an implementer of the interface defined by + * `interfaceId`. Support of the actual ERC165 interface is automatic and + * registering its interface id is not required. + * + * See `IERC165.supportsInterface`. + * + * Requirements: + * + * - `interfaceId` cannot be the ERC165 invalid interface (`0xffffffff`). + */ + function _registerInterface(bytes4 interfaceId) internal { + require(interfaceId != 0xffffffff, "ERC165: invalid interface id"); + _supportedInterfaces[interfaceId] = true; + } +} + + +/** + * @title ERC721 Non-Fungible Token Standard basic implementation + * @dev see https://eips.ethereum.org/EIPS/eip-721 + */ +contract ERC721 is ERC165, IERC721 { + using SafeMath for uint256; + using Address for address; + using Counters for Counters.Counter; + + // Equals to `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` + // which can be also obtained as `IERC721Receiver(0).onERC721Received.selector` + bytes4 private constant _ERC721_RECEIVED = 0x150b7a02; + + // Mapping from token ID to owner + mapping (uint256 => address) private _tokenOwner; + + // Mapping from token ID to approved address + mapping (uint256 => address) private _tokenApprovals; + + // Mapping from owner to number of owned token + mapping (address => Counters.Counter) private _ownedTokensCount; + + // Mapping from owner to operator approvals + mapping (address => mapping (address => bool)) private _operatorApprovals; + + bytes4 private constant _INTERFACE_ID_ERC721 = 0x80ac58cd; + /* + * 0x80ac58cd === + * bytes4(keccak256('balanceOf(address)')) ^ + * bytes4(keccak256('ownerOf(uint256)')) ^ + * bytes4(keccak256('approve(address,uint256)')) ^ + * bytes4(keccak256('getApproved(uint256)')) ^ + * bytes4(keccak256('setApprovalForAll(address,bool)')) ^ + * bytes4(keccak256('isApprovedForAll(address,address)')) ^ + * bytes4(keccak256('transferFrom(address,address,uint256)')) ^ + * bytes4(keccak256('safeTransferFrom(address,address,uint256)')) ^ + * bytes4(keccak256('safeTransferFrom(address,address,uint256,bytes)')) + */ + + constructor () public { + // register the supported interfaces to conform to ERC721 via ERC165 + _registerInterface(_INTERFACE_ID_ERC721); + } + + /** + * @dev Gets the balance of the specified address + * @param owner address to query the balance of + * @return uint256 representing the amount owned by the passed address + */ + function balanceOf(address owner) public view returns (uint256) { + require(owner != address(0)); + return _ownedTokensCount[owner].current(); + } + + /** + * @dev Gets the owner of the specified token ID + * @param tokenId uint256 ID of the token to query the owner of + * @return address currently marked as the owner of the given token ID + */ + function ownerOf(uint256 tokenId) public view returns (address) { + address owner = _tokenOwner[tokenId]; + require(owner != address(0)); + return owner; + } + + /** + * @dev Approves another address to transfer the given token ID + * The zero address indicates there is no approved address. + * There can only be one approved address per token at a given time. + * Can only be called by the token owner or an approved operator. + * @param to address to be approved for the given token ID + * @param tokenId uint256 ID of the token to be approved + */ + function approve(address to, uint256 tokenId) public { + address owner = ownerOf(tokenId); + require(to != owner); + require(msg.sender == owner || isApprovedForAll(owner, msg.sender)); + + _tokenApprovals[tokenId] = to; + emit Approval(owner, to, tokenId); + } + + /** + * @dev Gets the approved address for a token ID, or zero if no address set + * Reverts if the token ID does not exist. + * @param tokenId uint256 ID of the token to query the approval of + * @return address currently approved for the given token ID + */ + function getApproved(uint256 tokenId) public view returns (address) { + require(_exists(tokenId)); + return _tokenApprovals[tokenId]; + } + + /** + * @dev Sets or unsets the approval of a given operator + * An operator is allowed to transfer all tokens of the sender on their behalf + * @param to operator address to set the approval + * @param approved representing the status of the approval to be set + */ + function setApprovalForAll(address to, bool approved) public { + require(to != msg.sender); + _operatorApprovals[msg.sender][to] = approved; + emit ApprovalForAll(msg.sender, to, approved); + } + + /** + * @dev Tells whether an operator is approved by a given owner + * @param owner owner address which you want to query the approval of + * @param operator operator address which you want to query the approval of + * @return bool whether the given operator is approved by the given owner + */ + function isApprovedForAll(address owner, address operator) public view returns (bool) { + return _operatorApprovals[owner][operator]; + } + + /** + * @dev Transfers the ownership of a given token ID to another address + * Usage of this method is discouraged, use `safeTransferFrom` whenever possible + * Requires the msg.sender to be the owner, approved, or operator + * @param from current owner of the token + * @param to address to receive the ownership of the given token ID + * @param tokenId uint256 ID of the token to be transferred + */ + function transferFrom(address from, address to, uint256 tokenId) public { + require(_isApprovedOrOwner(msg.sender, tokenId)); + + _transferFrom(from, to, tokenId); + } + + /** + * @dev Safely transfers the ownership of a given token ID to another address + * If the target address is a contract, it must implement `onERC721Received`, + * which is called upon a safe transfer, and return the magic value + * `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`; otherwise, + * the transfer is reverted. + * Requires the msg.sender to be the owner, approved, or operator + * @param from current owner of the token + * @param to address to receive the ownership of the given token ID + * @param tokenId uint256 ID of the token to be transferred + */ + function safeTransferFrom(address from, address to, uint256 tokenId) public { + safeTransferFrom(from, to, tokenId, ""); + } + + /** + * @dev Safely transfers the ownership of a given token ID to another address + * If the target address is a contract, it must implement `onERC721Received`, + * which is called upon a safe transfer, and return the magic value + * `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`; otherwise, + * the transfer is reverted. + * Requires the msg.sender to be the owner, approved, or operator + * @param from current owner of the token + * @param to address to receive the ownership of the given token ID + * @param tokenId uint256 ID of the token to be transferred + * @param _data bytes data to send along with a safe transfer check + */ + function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory _data) public { + transferFrom(from, to, tokenId); + require(_checkOnERC721Received(from, to, tokenId, _data)); + } + + /** + * @dev Returns whether the specified token exists + * @param tokenId uint256 ID of the token to query the existence of + * @return bool whether the token exists + */ + function _exists(uint256 tokenId) internal view returns (bool) { + address owner = _tokenOwner[tokenId]; + return owner != address(0); + } + + /** + * @dev Returns whether the given spender can transfer a given token ID + * @param spender address of the spender to query + * @param tokenId uint256 ID of the token to be transferred + * @return bool whether the msg.sender is approved for the given token ID, + * is an operator of the owner, or is the owner of the token + */ + function _isApprovedOrOwner(address spender, uint256 tokenId) internal view returns (bool) { + address owner = ownerOf(tokenId); + return (spender == owner || getApproved(tokenId) == spender || isApprovedForAll(owner, spender)); + } + + /** + * @dev Internal function to mint a new token + * Reverts if the given token ID already exists + * @param to The address that will own the minted token + * @param tokenId uint256 ID of the token to be minted + */ + function _mint(address to, uint256 tokenId) internal { + require(to != address(0)); + require(!_exists(tokenId)); + + _tokenOwner[tokenId] = to; + _ownedTokensCount[to].increment(); + + emit Transfer(address(0), to, tokenId); + } + + /** + * @dev Internal function to burn a specific token + * Reverts if the token does not exist + * Deprecated, use _burn(uint256) instead. + * @param owner owner of the token to burn + * @param tokenId uint256 ID of the token being burned + */ + function _burn(address owner, uint256 tokenId) internal { + require(ownerOf(tokenId) == owner); + + _clearApproval(tokenId); + + _ownedTokensCount[owner].decrement(); + _tokenOwner[tokenId] = address(0); + + emit Transfer(owner, address(0), tokenId); + } + + /** + * @dev Internal function to burn a specific token + * Reverts if the token does not exist + * @param tokenId uint256 ID of the token being burned + */ + function _burn(uint256 tokenId) internal { + _burn(ownerOf(tokenId), tokenId); + } + + /** + * @dev Internal function to transfer ownership of a given token ID to another address. + * As opposed to transferFrom, this imposes no restrictions on msg.sender. + * @param from current owner of the token + * @param to address to receive the ownership of the given token ID + * @param tokenId uint256 ID of the token to be transferred + */ + function _transferFrom(address from, address to, uint256 tokenId) internal { + require(ownerOf(tokenId) == from); + require(to != address(0)); + + _clearApproval(tokenId); + + _ownedTokensCount[from].decrement(); + _ownedTokensCount[to].increment(); + + _tokenOwner[tokenId] = to; + + emit Transfer(from, to, tokenId); + } + + /** + * @dev Internal function to invoke `onERC721Received` on a target address + * The call is not executed if the target address is not a contract + * @param from address representing the previous owner of the given token ID + * @param to target address that will receive the tokens + * @param tokenId uint256 ID of the token to be transferred + * @param _data bytes optional data to send along with the call + * @return bool whether the call correctly returned the expected magic value + */ + function _checkOnERC721Received(address from, address to, uint256 tokenId, bytes memory _data) + internal returns (bool) + { + if (!to.isContract()) { + return true; + } + + bytes4 retval = IERC721Receiver(to).onERC721Received(msg.sender, from, tokenId, _data); + return (retval == _ERC721_RECEIVED); + } + + /** + * @dev Private function to clear current approval of a given token ID + * @param tokenId uint256 ID of the token to be transferred + */ + function _clearApproval(uint256 tokenId) private { + if (_tokenApprovals[tokenId] != address(0)) { + _tokenApprovals[tokenId] = address(0); + } + } +} + + +/** + * @title ERC-721 Non-Fungible Token with optional enumeration extension logic + * @dev See https://eips.ethereum.org/EIPS/eip-721 + */ +contract ERC721Enumerable is ERC165, ERC721, IERC721Enumerable { + // Mapping from owner to list of owned token IDs + mapping(address => uint256[]) private _ownedTokens; + + // Mapping from token ID to index of the owner tokens list + mapping(uint256 => uint256) private _ownedTokensIndex; + + // Array with all token ids, used for enumeration + uint256[] private _allTokens; + + // Mapping from token id to position in the allTokens array + mapping(uint256 => uint256) private _allTokensIndex; + + /* + * bytes4(keccak256('totalSupply()')) == 0x18160ddd + * bytes4(keccak256('tokenOfOwnerByIndex(address,uint256)')) == 0x2f745c59 + * bytes4(keccak256('tokenByIndex(uint256)')) == 0x4f6ccce7 + * + * => 0x18160ddd ^ 0x2f745c59 ^ 0x4f6ccce7 == 0x780e9d63 + */ + bytes4 private constant _INTERFACE_ID_ERC721_ENUMERABLE = 0x780e9d63; + + /** + * @dev Constructor function. + */ + constructor () public { + // register the supported interface to conform to ERC721Enumerable via ERC165 + _registerInterface(_INTERFACE_ID_ERC721_ENUMERABLE); + } + + /** + * @dev Gets the token ID at a given index of the tokens list of the requested owner. + * @param owner address owning the tokens list to be accessed + * @param index uint256 representing the index to be accessed of the requested tokens list + * @return uint256 token ID at the given index of the tokens list owned by the requested address + */ + function tokenOfOwnerByIndex(address owner, uint256 index) public view returns (uint256) { + require(index < balanceOf(owner), "ERC721Enumerable: owner index out of bounds"); + return _ownedTokens[owner][index]; + } + + /** + * @dev Gets the total amount of tokens stored by the contract. + * @return uint256 representing the total amount of tokens + */ + function totalSupply() public view returns (uint256) { + return _allTokens.length; + } + + /** + * @dev Gets the token ID at a given index of all the tokens in this contract + * Reverts if the index is greater or equal to the total number of tokens. + * @param index uint256 representing the index to be accessed of the tokens list + * @return uint256 token ID at the given index of the tokens list + */ + function tokenByIndex(uint256 index) public view returns (uint256) { + require(index < totalSupply(), "ERC721Enumerable: global index out of bounds"); + return _allTokens[index]; + } + + /** + * @dev Internal function to transfer ownership of a given token ID to another address. + * As opposed to transferFrom, this imposes no restrictions on msg.sender. + * @param from current owner of the token + * @param to address to receive the ownership of the given token ID + * @param tokenId uint256 ID of the token to be transferred + */ + function _transferFrom(address from, address to, uint256 tokenId) internal { + super._transferFrom(from, to, tokenId); + + _removeTokenFromOwnerEnumeration(from, tokenId); + + _addTokenToOwnerEnumeration(to, tokenId); + } + + /** + * @dev Internal function to mint a new token. + * Reverts if the given token ID already exists. + * @param to address the beneficiary that will own the minted token + * @param tokenId uint256 ID of the token to be minted + */ + function _mint(address to, uint256 tokenId) internal { + super._mint(to, tokenId); + + _addTokenToOwnerEnumeration(to, tokenId); + + _addTokenToAllTokensEnumeration(tokenId); + } + + /** + * @dev Internal function to burn a specific token. + * Reverts if the token does not exist. + * Deprecated, use _burn(uint256) instead. + * @param owner owner of the token to burn + * @param tokenId uint256 ID of the token being burned + */ + function _burn(address owner, uint256 tokenId) internal { + super._burn(owner, tokenId); + + _removeTokenFromOwnerEnumeration(owner, tokenId); + // Since tokenId will be deleted, we can clear its slot in _ownedTokensIndex to trigger a gas refund + _ownedTokensIndex[tokenId] = 0; + + _removeTokenFromAllTokensEnumeration(tokenId); + } + + /** + * @dev Private function to add a token to this extension's ownership-tracking data structures. + * @param to address representing the new owner of the given token ID + * @param tokenId uint256 ID of the token to be added to the tokens list of the given address + */ + function _addTokenToOwnerEnumeration(address to, uint256 tokenId) private { + _ownedTokensIndex[tokenId] = _ownedTokens[to].length; + _ownedTokens[to].push(tokenId); + } + + /** + * @dev Private function to add a token to this extension's token tracking data structures. + * @param tokenId uint256 ID of the token to be added to the tokens list + */ + function _addTokenToAllTokensEnumeration(uint256 tokenId) private { + _allTokensIndex[tokenId] = _allTokens.length; + _allTokens.push(tokenId); + } + + /** + * @dev Private function to remove a token from this extension's ownership-tracking data structures. Note that + * while the token is not assigned a new owner, the _ownedTokensIndex mapping is _not_ updated: this allows for + * gas optimizations e.g. when performing a transfer operation (avoiding double writes). + * This has O(1) time complexity, but alters the order of the _ownedTokens array. + * @param from address representing the previous owner of the given token ID + * @param tokenId uint256 ID of the token to be removed from the tokens list of the given address + */ + function _removeTokenFromOwnerEnumeration(address from, uint256 tokenId) private { + // To prevent a gap in from's tokens array, we store the last token in the index of the token to delete, and + // then delete the last slot (swap and pop). + + uint256 lastTokenIndex = _ownedTokens[from].length.sub(1); + uint256 tokenIndex = _ownedTokensIndex[tokenId]; + + // When the token to delete is the last token, the swap operation is unnecessary + if (tokenIndex != lastTokenIndex) { + uint256 lastTokenId = _ownedTokens[from][lastTokenIndex]; + + _ownedTokens[from][tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token + _ownedTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index + } + + // This also deletes the contents at the last position of the array + _ownedTokens[from].length--; + + // Note that _ownedTokensIndex[tokenId] hasn't been cleared: it still points to the old slot (now occupied by + // lastTokenId, or just over the end of the array if the token was the last one). + } + + /** + * @dev Private function to remove a token from this extension's token tracking data structures. + * This has O(1) time complexity, but alters the order of the _allTokens array. + * @param tokenId uint256 ID of the token to be removed from the tokens list + */ + function _removeTokenFromAllTokensEnumeration(uint256 tokenId) private { + // To prevent a gap in the tokens array, we store the last token in the index of the token to delete, and + // then delete the last slot (swap and pop). + + uint256 lastTokenIndex = _allTokens.length.sub(1); + uint256 tokenIndex = _allTokensIndex[tokenId]; + + // When the token to delete is the last token, the swap operation is unnecessary. However, since this occurs so + // rarely (when the last minted token is burnt) that we still do the swap here to avoid the gas cost of adding + // an 'if' statement (like in _removeTokenFromOwnerEnumeration) + uint256 lastTokenId = _allTokens[lastTokenIndex]; + + _allTokens[tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token + _allTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index + + // This also deletes the contents at the last position of the array + _allTokens.length--; + _allTokensIndex[tokenId] = 0; + } +} + + +/** + * @title HomeWork (version 1) + * @author 0age + * @notice Homework is a utility to find, share, and reuse "home" addresses for + * contracts. Anyone can work to find a new home address by searching for keys, + * a 32-byte value with the first 20 bytes equal to the finder's calling address + * (or derived by hashing an arbitrary 32-byte salt and the caller's address), + * and can then deploy any contract they like (even one with a constructor) to + * the address, or mint an ERC721 token that the owner can redeem that will then + * allow them to do the same. Also, if the contract is `SELFDESTRUCT`ed, a new + * contract can be redeployed by the current controller to the same address! + * @dev This contract allows contract addresses to be located ahead of time, and + * for arbitrary bytecode to be deployed (and redeployed if so desired, i.e. + * metamorphic contracts) to the located address by a designated controller. To + * enable this, the contract first deploys an "initialization-code-in-runtime" + * contract, with the creation code of the contract you want to deploy stored in + * RUNTIME code. Then, to deploy the actual contract, it retrieves the address + * of the storage contract and `DELEGATECALL`s into it to execute the init code + * and, if successful, retrieves and returns the contract runtime code. Rather + * than using a located address directly, you can also lock it in the contract + * and mint and ERC721 token for it, which can then be redeemed in order to gain + * control over deployment to the address (note that tokens may not be minted if + * the contract they control currently has a deployed contract at that address). + * Once a contract undergoes metamorphosis, all existing storage will be deleted + * and any existing contract code will be replaced with the deployed contract + * code of the new implementation contract. The mechanisms behind this contract + * are highly experimental - proceed with caution and please share any exploits + * or optimizations you discover. + */ +contract HomeWork is IHomeWork, ERC721Enumerable, IERC721Metadata, IERC1412 { + // Allocate storage to track the current initialization-in-runtime contract. + address private _initializationRuntimeStorageContract; + + // Finder of home address with lowest uint256 value can recover lost funds. + bytes32 private _highScoreKey; + + // Track information on the Home address corresponding to each key. + mapping (bytes32 => HomeAddress) private _home; + + // Provide optional reverse-lookup for key derivation of a given home address. + mapping (address => KeyInformation) private _key; + + // Set 0xff + address(this) as a constant to use when deriving home addresses. + bytes21 private constant _FF_AND_THIS_CONTRACT = bytes21( + 0xff0000000000001b84b1cb32787B0D64758d019317 + ); + + // Set the address of the tokenURI runtime storage contract as a constant. + address private constant _URI_END_SEGMENT_STORAGE = address( + 0x000000000071C1c84915c17BF21728BfE4Dac3f3 + ); + + // Deploy arbitrary contracts to home addresses using metamorphic init code. + bytes32 private constant _HOME_INIT_CODE = bytes32( + 0x5859385958601c335a585952fa1582838382515af43d3d93833e601e57fd5bf3 + ); + + // Compute hash of above metamorphic init code in order to compute addresses. + bytes32 private constant _HOME_INIT_CODE_HASH = bytes32( + 0x7816562e7f85866cae07183593075f3b5ec32aeff914a0693e20aaf39672babc + ); + + // Write arbitrary code to a contract's runtime using the following prelude. + bytes11 private constant _ARBITRARY_RUNTIME_PRELUDE = bytes11( + 0x600b5981380380925939f3 + ); + + // Set EIP165 interface IDs as constants (already set 165 and 721+enumerable). + bytes4 private constant _INTERFACE_ID_HOMEWORK = 0xe5399799; + /* this.deploy.selector ^ this.lock.selector ^ this.redeem.selector ^ + this.assignController.selector ^ this.relinquishControl.selector ^ + this.redeemAndDeploy.selector ^ this.deriveKey.selector ^ + this.deriveKeyAndLock.selector ^ + this.deriveKeyAndAssignController.selector ^ + this.deriveKeyAndRelinquishControl.selector ^ + this.setReverseLookup.selector ^ this.setDerivedReverseLookup.selector ^ + this.deployRuntimeStorageContract.selector ^ + this.deployViaExistingRuntimeStorageContract.selector ^ + this.redeemAndDeployViaExistingRuntimeStorageContract.selector ^ + this.deriveKeyAndDeploy.selector ^ + this.deriveKeyAndDeployViaExistingRuntimeStorageContract.selector ^ + this.batchLock.selector ^ this.deriveKeysAndBatchLock.selector ^ + this.batchLock_63efZf.selector ^ this.claimHighScore.selector ^ + this.recover.selector ^ this.isDeployable.selector ^ + this.getHighScore.selector ^ this.getHomeAddressInformation.selector ^ + this.hasNeverBeenDeployed.selector ^ this.reverseLookup.selector ^ + this.getDerivedKey.selector ^ this.getHomeAddress.selector ^ + this.getMetamorphicDelegatorInitializationCode.selector ^ + this.getMetamorphicDelegatorInitializationCodeHash.selector ^ + this.getArbitraryRuntimeCodePrelude.selector == 0xe5399799 + */ + + bytes4 private constant _INTERFACE_ID_ERC721_METADATA = 0x5b5e139f; + + bytes4 private constant _INTERFACE_ID_ERC1412_BATCH_TRANSFERS = 0x2b89bcaa; + + // Set name of this contract as a constant (hex encoding is to support emoji). + string private constant _NAME = ( + hex"486f6d65576f726b20f09f8fa0f09f9ba0efb88f" + ); + + // Set symbol of this contract as a constant. + string private constant _SYMBOL = "HWK"; + + // Set the start of each token URI for issued ERC721 tokens as a constant. + bytes private constant _URI_START_SEGMENT = abi.encodePacked( + hex"646174613a6170706c69636174696f6e2f6a736f6e2c7b226e616d65223a22486f6d65", + hex"253230416464726573732532302d2532303078" + ); /* data:application/json,{"name":"Home%20Address%20-%200x */ + + // Store reused revert messages as constants. + string private constant _ACCOUNT_EXISTS = string( + "Only non-existent accounts can be deployed or used to mint tokens." + ); + + string private constant _ONLY_CONTROLLER = string( + "Only the designated controller can call this function." + ); + + string private constant _NO_INIT_CODE_SUPPLIED = string( + "Cannot deploy a contract with no initialization code supplied." + ); + + /** + * @notice In the constructor, verify that deployment addresses are correct + * and that supplied constant hash value of the contract creation code used to + * deploy arbitrary contracts to home addresses is valid, and set an initial + * high score key with the null address as the high score "holder". ERC165 + * supported interfaces are all registered during initizialization as well. + */ + constructor() public { + // Verify that the deployment address is set correctly as a constant. + assert(address(this) == address(uint160(uint168(_FF_AND_THIS_CONTRACT)))); + + // Verify the derivation of the deployment address. + bytes32 initialDeployKey = bytes32( + 0x486f6d65576f726b20f09f8fa0f09f9ba0efb88faa3c548a76f9bd3c000c0000 + ); + assert(address(this) == address( + uint160( // Downcast to match the address type. + uint256( // Convert to uint to truncate upper digits. + keccak256( // Compute the CREATE2 hash using 4 inputs. + abi.encodePacked( // Pack all inputs to the hash together. + bytes1(0xff), // Start with 0xff to distinguish from RLP. + msg.sender, // The deployer will be the caller. + initialDeployKey, // Pass in the supplied key as the salt. + _HOME_INIT_CODE_HASH // The metamorphic initialization code hash. + ) + ) + ) + ) + )); + + // Verify the derivation of the tokenURI runtime storage address. + bytes32 uriDeployKey = bytes32( + 0x486f6d65576f726b202d20746f6b656e55524920c21352fee5a62228db000000 + ); + bytes32 uriInitCodeHash = bytes32( + 0xdea98294867e3fdc48eb5975ecc53a79e2e1ea6e7e794137a9c34c4dd1565ba2 + ); + assert(_URI_END_SEGMENT_STORAGE == address( + uint160( // Downcast to match the address type. + uint256( // Convert to uint to truncate upper digits. + keccak256( // Compute the CREATE2 hash using 4 inputs. + abi.encodePacked( // Pack all inputs to the hash together. + bytes1(0xff), // Start with 0xff to distinguish from RLP. + msg.sender, // The deployer will be the caller. + uriDeployKey, // Pass in the supplied key as the salt. + uriInitCodeHash // The storage contract init code hash. + ) + ) + ) + ) + )); + + // Verify that the correct runtime code is at the tokenURI storage contract. + bytes32 expectedRuntimeStorageHash = bytes32( + 0x8834602968080bb1df9c44c9834c0a93533b72bbfa3865ee2c5be6a0c4125fc3 + ); + address runtimeStorage = _URI_END_SEGMENT_STORAGE; + bytes32 runtimeStorageHash; + assembly { runtimeStorageHash := extcodehash(runtimeStorage) } + assert(runtimeStorageHash == expectedRuntimeStorageHash); + + // Verify that the supplied hash for the metamorphic init code is valid. + assert(keccak256(abi.encode(_HOME_INIT_CODE)) == _HOME_INIT_CODE_HASH); + + // Set an initial high score key with the null address as the submitter. + _highScoreKey = bytes32( + 0x0000000000000000000000000000000000000000ffffffffffffffffffffffff + ); + + // Register EIP165 interface for HomeWork. + _registerInterface(_INTERFACE_ID_HOMEWORK); + + // Register EIP165 interface for ERC721 metadata. + _registerInterface(_INTERFACE_ID_ERC721_METADATA); + + // Register EIP165 interface for ERC1412 (batch transfers). + _registerInterface(_INTERFACE_ID_ERC1412_BATCH_TRANSFERS); + } + + /** + * @notice Deploy a new contract with the desired initialization code 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 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)); + } + + /** + * @notice Record a key that corresponds to a given home address by supplying + * said key and using it to derive the address. This enables 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)); + } + } + + /** + * @notice Safely transfers the ownership of a group of token IDs to another + * address in a batch. If the target address is a contract, it must implement + * `onERC721Received`, called upon a safe transfer, and return the magic value + * `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`; + * otherwise, or if another error occurs, the entire batch is reverted. + * Requires msg.sender to be the owner, approved, or operator of the tokens. + * @param from address The current owner of the tokens. + * @param to address The account to receive ownership of the given tokens. + * @param tokenIds uint256[] ID of the tokens to be transferred. + */ + function safeBatchTransferFrom( + address from, + address to, + uint256[] calldata tokenIds + ) + external + { + // Track each token ID in the batch. + uint256 tokenId; + + // Iterate over each supplied token ID. + for (uint256 i = 0; i < tokenIds.length; i++) { + // Set the current token ID. + tokenId = tokenIds[i]; + + // Perform the token transfer. + safeTransferFrom(from, to, tokenId); + } + } + + /** + * @notice Safely transfers the ownership of a group of token IDs to another + * address in a batch. If the target address is a contract, it must implement + * `onERC721Received`, called upon a safe transfer, and return the magic value + * `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`; + * otherwise, or if another error occurs, the entire batch is reverted. + * Requires msg.sender to be the owner, approved, or operator of the tokens. + * @param from address The current owner of the tokens. + * @param to address The account to receive ownership of the given tokens. + * @param tokenIds uint256[] ID of the tokens to be transferred. + * @param data bytes A data payload to include with each transfer. + */ + function safeBatchTransferFrom( + address from, + address to, + uint256[] calldata tokenIds, + bytes calldata data + ) + external + { + // Track each token ID in the batch. + uint256 tokenId; + + // Iterate over each supplied token ID. + for (uint256 i = 0; i < tokenIds.length; i++) { + // Set the current token ID. + tokenId = tokenIds[i]; + + // Perform the token transfer. + safeTransferFrom(from, to, tokenId, data); + } + } + + /** + * @notice Efficient version of `batchLock` that uses less gas. The first 20 + * bytes of each key are automatically populated using msg.sender, and the + * remaining key segments are passed in as a packed byte array, using twelve + * bytes per segment, with a function selector of 0x00000000 followed by a + * twenty-byte segment for the desired owner of the minted ERC721 tokens. Note + * that an attempt to lock a key that is not controlled or with its contract + * already deployed will cause the entire batch to revert. Checks on whether + * the owner is a valid ERC721 receiver are also skipped, similar to using + * `transferFrom` instead of `safeTransferFrom`. + */ + function batchLock_63efZf(/* packed owner and key segments */) external { + // Get the owner from calldata, located at bytes 4-23 (sig is bytes 0-3). + address owner; + + // Determine number of 12-byte key segments in calldata from byte 24 on. + uint256 passedSaltSegments; + + // Get the owner and calculate the total number of key segments. + assembly { + owner := shr(0x60, calldataload(4)) // comes after sig + passedSaltSegments := div(sub(calldatasize, 24), 12) // after sig & owner + } + + // Track each key, located at each 12-byte segment from byte 24 on. + bytes32 key; + + // Iterate through each provided key segment argument. + for (uint256 i; i < passedSaltSegments; i++) { + // Construct keys by concatenating msg.sender with each key segment. + assembly { + key := add( // Combine msg.sender & provided key. + shl(0x60, caller), // Place msg.sender at start of word. + shr(0xa0, calldataload(add(24, mul(i, 12)))) // Segment at end. + ) + } + + // Ensure that the key does not currently have a deployed contract. + require(_isNotDeployed(key), _ACCOUNT_EXISTS); + + // Ensure that the caller is the controller of the 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); + + // 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 Perform a dry-run of the deployment of a contract using a given key + * and revert on successful deployment. It cannot be called from outside the + * contract (even though it is marked as external). + * @param key bytes32 The unique value used to derive the home address. + * @dev This contract is called by `_isNotDeployable` in extreme cases where + * the deployability of the contract cannot be determined conclusively. + */ + function staticCreate2Check(bytes32 key) external { + require( + msg.sender == address(this), + "This function can only be called by this contract." + ); + + assembly { + // Write the 32-byte metamorphic initialization code to scratch space. + mstore( + 0, + 0x5859385958601c335a585952fa1582838382515af43d3d93833e601e57fd5bf3 + ) + + // Call `CREATE2` using metamorphic init code with supplied key as salt. + let deploymentAddress := create2(0, 0, 32, key) + + // Revert and return the metamorphic init code on successful deployment. + if deploymentAddress { + revert(0, 32) + } + } + } + + /** + * @notice Submit a key to claim the "high score" - the lower the uint160 + * value of the key's home address, the higher the score. The high score + * holder has the exclusive right to recover lost ether and tokens on this + * contract. + * @param key bytes32 The unique value used to derive the home address that + * will determine the resultant score. + * @dev The high score must be claimed by a direct key (one that is submitted + * by setting the first 20 bytes of the key to the address of the submitter) + * and not by a derived key, and is non-transferrable. If you want to help + * people recover their lost tokens, you might consider deploying a contract + * to the high score address (probably a metamorphic one so that you can use + * the home address later) with your contact information. + */ + function claimHighScore(bytes32 key) external { + require( + msg.sender == address(bytes20(key)), + "Only submitters directly encoded in a given key may claim a high score." + ); + + // Derive the "home address" of the current high score key. + address currentHighScore = _getHomeAddress(_highScoreKey); + + // Derive the "home address" of the new high score key. + address newHighScore = _getHomeAddress(key); + + // Use addresses to ensure that supplied key is in fact a new high score. + require( + uint160(newHighScore) < uint160(currentHighScore), + "Submitted high score is not better than the current high score." + ); + + // Set the new high score to the supplied key. + _highScoreKey = key; + + // The score is equal to (2^160 - 1) - ("home address" of high score key). + uint256 score = uint256(uint160(-1) - uint160(newHighScore)); + + // Emit an event to signify that a new high score has been reached. + emit NewHighScore(key, msg.sender, score); + } + + /** + * @notice Transfer any ether or ERC20 tokens that have somehow ended up at + * this contract by specifying a token address (set to the null address for + * ether) as well as a recipient address. Only the high score holder can + * recover lost ether and tokens on this contract. + * @param token address The contract address of the ERC20 token to recover, or + * the null address for recovering Ether. + * @param recipient address payable The account where recovered funds should + * be transferred. + * @dev If you are trying to recover funds that were accidentally sent into + * this contract, see if you can contact the holder of the current high score, + * found by calling `getHighScore`. Better yet, try to find a new high score + * yourself! + */ + function recover(IERC20 token, address payable recipient) external { + require( + msg.sender == address(bytes20(_highScoreKey)), + "Only the current high score holder may recover tokens." + ); + + if (address(token) == address(0)) { + // Recover ETH if the token's contract address is set to the null address. + recipient.transfer(address(this).balance); + } else { + // Determine the given ERC20 token balance and transfer to the recipient. + uint256 balance = token.balanceOf(address(this)); + token.transfer(recipient, balance); + } + } + + /** + * @notice "View" function to determine if a contract can currently be + * deployed to a home address given the corresponding key. A contract is only + * deployable if no account currently exists at the address - any existing + * contract must be destroyed via `SELFDESTRUCT` before a new contract can be + * deployed to a home address. This method does not modify state but is + * inaccessible via staticcall. + * @param key bytes32 The unique value used to derive the home address. + * @return A boolean signifying if a contract can be deployed to the home + * address that corresponds to the provided key. + * @dev This will not detect if a contract is not deployable due control + * having been relinquished on the key. + */ + function isDeployable(bytes32 key) + external + /* view */ + returns (bool deployable) + { + deployable = _isNotDeployed(key); + } + + /** + * @notice View function to get the current "high score", or the lowest + * uint160 value of a home address of all keys submitted. The high score + * holder has the exclusive right to recover lost ether and tokens on this + * contract. + * @return The current high score holder, their score, and the submitted key. + */ + function getHighScore() + external + view + returns (address holder, uint256 score, bytes32 key) + { + // Get the key and subbmitter holding the current high score. + key = _highScoreKey; + holder = address(bytes20(key)); + + // The score is equal to (2^160 - 1) - ("home address" of high score key). + score = uint256(uint160(-1) - uint160(_getHomeAddress(key))); + } + + /** + * @notice View function to get information on a home address given the + * corresponding key. + * @param key bytes32 The unique value used to derive the home address. + * @return The home address, the current controller of the address, the number + * of times the home address has been deployed to, and the code hash of the + * runtime currently found at the home address, if any. + * @dev There is also an `isDeployable` method for determining if a contract + * can be deployed to the address, but in extreme cases it must actually + * perform a dry-run to determine if the contract is deployable, which means + * that it does not support staticcalls. There is also a convenience method, + * `hasNeverBeenDeployed`, but the information it conveys can be determined + * from this method alone as well. + */ + function getHomeAddressInformation(bytes32 key) + external + view + returns ( + address homeAddress, + address controller, + uint256 deploys, + bytes32 currentRuntimeCodeHash + ) + { + // Derive home address and retrieve other information using supplied key. + homeAddress = _getHomeAddress(key); + HomeAddress memory home = _home[key]; + + // If the home address has not been seen before, use the default controller. + if (!home.exists) { + controller = address(bytes20(key)); + } else { + controller = home.controller; + } + + // Retrieve the count of total deploys to the home address. + deploys = home.deploys; + + // Retrieve keccak256 hash of runtime code currently at the home address. + assembly { currentRuntimeCodeHash := extcodehash(homeAddress) } + } + + /** + * @notice View function to determine if no contract has ever been deployed to + * a home address given the corresponding key. This can be used to ensure that + * a given key or corresponding token is "new" or not. + * @param key bytes32 The unique value used to derive the home address. + * @return A boolean signifying if a contract has never been deployed using + * the supplied key before. + */ + function hasNeverBeenDeployed(bytes32 key) + external + view + returns (bool neverBeenDeployed) + { + neverBeenDeployed = (_home[key].deploys == 0); + } + + /** + * @notice View function to search for a known key, salt, and/or submitter + * given a supplied home address. Keys can be controlled directly by an + * address that matches the first 20 bytes of the key, or they can be derived + * from a salt and a submitter - if the key is not a derived key, the salt and + * submitter fields will both have a value of zero. + * @param homeAddress address The home address to check for key information. + * @return The key, salt, and/or submitter used to deploy to the home address, + * assuming they have been submitted to the reverse lookup. + * @dev To populate these values, call `setReverseLookup` for cases where keys + * are used directly or are the only value known, or `setDerivedReverseLookup` + * for cases where keys are derived from a known salt and submitter. + */ + function reverseLookup(address homeAddress) + external + view + returns (bytes32 key, bytes32 salt, address submitter) + { + KeyInformation memory keyInformation = _key[homeAddress]; + key = keyInformation.key; + salt = keyInformation.salt; + submitter = keyInformation.submitter; + } + + /** + * @notice View function used by the metamorphic initialization code when + * deploying a contract to a home address. It returns the address of the + * runtime storage contract that holds the contract creation code, which the + * metamorphic creation code then `DELEGATECALL`s into in order to set up the + * contract and deploy the target runtime code. + * @return The current runtime storage contract that contains the target + * contract creation code. + * @dev This method is not meant to be part of the user-facing contract API, + * but is rather a mechanism for enabling the deployment of arbitrary code via + * fixed initialization code. The odd naming is chosen so that function + * selector will be 0x00000009 - that way, the metamorphic contract can simply + * use the `PC` opcode in order to push the selector to the stack. + */ + function getInitializationCodeFromContractRuntime_6CLUNS() + external + view + returns (address initializationRuntimeStorageContract) + { + // Return address of contract with initialization code set as runtime code. + initializationRuntimeStorageContract = _initializationRuntimeStorageContract; + } + + /** + * @notice View function to return an URI for a given token ID. Throws if the + * token ID does not exist. + * @param tokenId uint256 ID of the token to query. + * @return String representing the URI data encoding of JSON metadata. + * @dev The URI returned by this method takes the following form (with all + * returns and initial whitespace removed - it's just here for clarity): + * + * data:application/json,{ + * "name":"Home%20Address%20-%200x********************", + * "description":"< ... HomeWork NFT desription ... >", + * "image":"data:image/svg+xml;charset=utf-8;base64,< ... Image ... >"} + * + * where ******************** represents the checksummed home address that the + * token confers control over. + */ + function tokenURI(uint256 tokenId) + external + view + returns (string memory) + { + // Only return a URI for tokens that exist. + require(_exists(tokenId), "A token with the given ID does not exist."); + + // Get the home address that the given tokenId corresponds to. + address homeAddress = _getHomeAddress(bytes32(tokenId)); + + // Get the checksummed, ascii-encoded representation of the home address. + string memory asciiHomeAddress = _toChecksummedAsciiString(homeAddress); + + bytes memory uriEndSegment = _getTokenURIStorageRuntime(); + + // Insert checksummed address into URI in name and image fields and return. + return string( + abi.encodePacked( // Concatenate all the string segments together. + _URI_START_SEGMENT, // Data URI ID and initial formatting is constant. + asciiHomeAddress, // Checksummed home address is in the name field. + uriEndSegment // Description, image, and formatting is constant. + ) + ); + } + + /** + * @notice Pure function to get the token name. + * @return String representing the token name. + */ + function name() external pure returns (string memory) { + return _NAME; + } + + /** + * @notice Pure function to get the token symbol. + * @return String representing the token symbol. + */ + function symbol() external pure returns (string memory) { + return _SYMBOL; + } + + /** + * @notice Pure function to determine the key that is derived from a given + * salt and submitting address. + * @param salt bytes32 The salt value that is used to derive the key. + * @param submitter address The submitter of the salt value used to derive the + * key. + * @return The derived key. + */ + function getDerivedKey(bytes32 salt, address submitter) + external + pure + returns (bytes32 key) + { + // Derive the key using the supplied salt and submitter. + key = _deriveKey(salt, submitter); + } + + /** + * @notice Pure function to determine the home address that corresponds to + * a given key. + * @param key bytes32 The unique value used to derive the home address. + * @return The home address. + */ + function getHomeAddress(bytes32 key) + external + pure + returns (address homeAddress) + { + // Derive the home address using the supplied key. + homeAddress = _getHomeAddress(key); + } + + /** + * @notice Pure function for retrieving the metamorphic initialization code + * used to deploy arbitrary contracts to home addresses. Provided for easy + * verification and for use in other applications. + * @return The 32-byte metamorphic initialization code. + * @dev This metamorphic init code works via the "metamorphic delegator" + * mechanism, which is explained in greater detail at `_deployToHomeAddress`. + */ + function getMetamorphicDelegatorInitializationCode() + external + pure + returns (bytes32 metamorphicDelegatorInitializationCode) + { + metamorphicDelegatorInitializationCode = _HOME_INIT_CODE; + } + + /** + * @notice Pure function for retrieving the keccak256 of the metamorphic + * initialization code used to deploy arbitrary contracts to home addresses. + * This is the value that you should use, along with this contract's address + * and a caller address that you control, to mine for an partucular type of + * home address (such as one at a compact or gas-efficient address). + * @return The keccak256 hash of the metamorphic initialization code. + */ + function getMetamorphicDelegatorInitializationCodeHash() + external + pure + returns (bytes32 metamorphicDelegatorInitializationCodeHash) + { + metamorphicDelegatorInitializationCodeHash = _HOME_INIT_CODE_HASH; + } + + /** + * @notice Pure function for retrieving the prelude that will be inserted + * ahead of the code payload in order to deploy a runtime storage contract. + * @return The 11-byte "arbitrary runtime" prelude. + */ + function getArbitraryRuntimeCodePrelude() + external + pure + returns (bytes11 prelude) + { + prelude = _ARBITRARY_RUNTIME_PRELUDE; + } + + /** + * @notice Internal function for deploying a runtime storage contract given a + * particular payload. + * @return The address of the runtime storage contract. + * @dev To take the provided code payload and deploy a contract with that + * payload as its runtime code, use the following prelude: + * + * 0x600b5981380380925939f3... + * + * 00 60 push1 0b [11 -> offset] + * 02 59 msize [offset, 0] + * 03 81 dup2 [offset, 0, offset] + * 04 38 codesize [offset, 0, offset, codesize] + * 05 03 sub [offset, 0, codesize - offset] + * 06 80 dup1 [offset, 0, codesize - offset, codesize - offset] + * 07 92 swap3 [codesize - offset, 0, codesize - offset, offset] + * 08 59 msize [codesize - offset, 0, codesize - offset, offset, 0] + * 09 39 codecopy [codesize - offset, 0] + * 10 f3 return [] *init_code_in_runtime* + * ... init_code + */ + function _deployRuntimeStorageContract(bytes memory payload) + internal + returns (address runtimeStorageContract) + { + // Construct the contract creation code using the prelude and the payload. + bytes memory runtimeStorageContractCreationCode = abi.encodePacked( + _ARBITRARY_RUNTIME_PRELUDE, + payload + ); + + assembly { + // Get the location and length of the newly-constructed creation code. + let encoded_data := add(0x20, runtimeStorageContractCreationCode) + let encoded_size := mload(runtimeStorageContractCreationCode) + + // Deploy the runtime storage contract via standard `CREATE`. + runtimeStorageContract := create(0, encoded_data, encoded_size) + + // Pass along revert message if the contract did not deploy successfully. + if iszero(runtimeStorageContract) { + returndatacopy(0, 0, returndatasize) + revert(0, returndatasize) + } + } + + // Emit an event with address of newly-deployed runtime storage contract. + emit NewRuntimeStorageContract(runtimeStorageContract, keccak256(payload)); + } + + /** + * @notice Internal function for deploying arbitrary contract code to the home + * address corresponding to a suppied key via metamorphic initialization code. + * @return The home address and the hash of the deployed runtime code. + * @dev This deployment method uses the "metamorphic delegator" pattern, where + * it will retrieve the address of the contract that contains the target + * initialization code, then delegatecall into it, 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] + * 12 fa STATICCALL [0, 0, 1 => success] + * 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] + * 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 + ); + } + } + + /** + * @notice Internal pure function for converting the bytes representation of + * an address to a checksummed ASCII string. + * @param account address The account address to be converted. + * @return The checksummed account string in ASCII format. Note that leading + * "0x" is not included. + */ + function _toChecksummedAsciiString(address account) + internal + pure + returns (string memory checksummedAsciiString) + { + // Get capitalized characters in the checksum. + bool[40] memory caps = _getChecksumCapitalizedCharacters(account); + + // Create an in-memory fixed-size bytes array. + bytes memory asciiBytes = new bytes(40); + + // Declare variable types. + uint8 oneByte; + uint8 leftNibble; + uint8 rightNibble; + uint8 leftNibbleOffset; + uint8 rightNibbleOffset; + + // Convert account to bytes20. + bytes20 data = bytes20(account); + + // 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, 55 to A-F, & 87 to a-f. + if (leftNibble < 10) { + leftNibbleOffset = 48; + } else if (caps[i * 2]) { + leftNibbleOffset = 55; + } else { + leftNibbleOffset = 87; + } + + if (rightNibble < 10) { + rightNibbleOffset = 48; + } else { + rightNibbleOffset = caps[(i * 2) + 1] ? 55 : 87; // instrumentation fix + } + + asciiBytes[2 * i] = byte(leftNibble + leftNibbleOffset); + asciiBytes[2 * i + 1] = byte(rightNibble + rightNibbleOffset); + } + + checksummedAsciiString = string(asciiBytes); + } + + /** + * @notice Modifier to ensure that a contract is not currently deployed to the + * home address corresponding to a given key on the decorated function. + * @param key bytes32 The unique value used to derive the home address. + */ + modifier onlyEmpty(bytes32 key) { + require(_isNotDeployed(key), _ACCOUNT_EXISTS); + _; + } + + /** + * @notice Modifier to ensure that the caller of the decorated function is the + * controller of the home address corresponding to a given key. + * @param key bytes32 The unique value used to derive the home address. + */ + modifier onlyController(bytes32 key) { + require(_getController(key) == msg.sender, _ONLY_CONTROLLER); + _; + } + + /** + * @notice Modifier to track initial controllers and to count deploys, and to + * validate that only the designated controller has access to the decorated + * function. + * @param key bytes32 The unique value used to derive the home address. + */ + modifier onlyControllerDeployer(bytes32 key) { + HomeAddress storage home = _home[key]; + + // Set appropriate controller and increment contract deploy count at once. + if (!home.exists) { + home.exists = true; + home.controller = address(bytes20(key)); + home.deploys += 1; + } else { + home.deploys += 1; + } + + require(home.controller == msg.sender, _ONLY_CONTROLLER); + _; + } + + /** + * @notice Modifier to ensure that only the owner of the supplied ERC721 + * token, or an approved spender, can access the decorated function. + * @param tokenId uint256 The ID of the ERC721 token. + */ + modifier onlyTokenOwnerOrApprovedSpender(uint256 tokenId) { + require( + _isApprovedOrOwner(msg.sender, tokenId), + "Only the token owner or an approved spender may call this function." + ); + _; + } +} + +/** + * @title HomeWork Deployer (alpha version) + * @author 0age + * @notice This contract is a stripped-down version of HomeWork that is used to + * deploy HomeWork itself. + * HomeWork Deploy code at runtime: 0x7Cf7708ab4A064B14B02F34aecBd2511f3605395 + * HomeWork Runtime code at: 0x0000000000001b84b1cb32787b0d64758d019317 + */ +contract HomeWorkDeployer { + // Fires when HomeWork has been deployed. + event HomeWorkDeployment(address homeAddress, bytes32 key); + + // Fires HomeWork's initialization-in-runtime storage contract is deployed. + event StorageContractDeployment(address runtimeStorageContract); + + // Allocate storage to track the current initialization-in-runtime contract. + address private _initializationRuntimeStorageContract; + + // Once HomeWork has been deployed, disable this contract. + bool private _disabled; + + // Write arbitrary code to a contract's runtime using the following prelude. + bytes11 private constant _ARBITRARY_RUNTIME_PRELUDE = bytes11( + 0x600b5981380380925939f3 + ); + + /** + * @notice Perform phase one of the deployment. + * @param code bytes The contract creation code for HomeWork. + */ + function phaseOne(bytes calldata code) external onlyUntilDisabled { + // Deploy payload to the runtime storage contract and set the address. + _initializationRuntimeStorageContract = _deployRuntimeStorageContract( + bytes32(0), + code + ); + } + + /** + * @notice Perform phase two of the deployment (tokenURI data). + * @param key bytes32 The salt to provide to create2. + */ + function phaseTwo(bytes32 key) external onlyUntilDisabled { + // Deploy runtime storage contract with the string used to construct end of + // token URI for issued ERC721s (data URI with a base64-encoded jpeg image). + bytes memory code = abi.encodePacked( + hex"222c226465736372697074696f6e223a22546869732532304e465425323063616e25", + hex"3230626525323072656465656d65642532306f6e253230486f6d65576f726b253230", + hex"746f2532306772616e7425323061253230636f6e74726f6c6c657225323074686525", + hex"32306578636c75736976652532307269676874253230746f2532306465706c6f7925", + hex"3230636f6e7472616374732532307769746825323061726269747261727925323062", + hex"797465636f6465253230746f25323074686525323064657369676e61746564253230", + hex"686f6d65253230616464726573732e222c22696d616765223a22646174613a696d61", + hex"67652f7376672b786d6c3b636861727365743d7574662d383b6261736536342c5048", + hex"4e325a79423462577875637a30696148523063446f764c336433647935334d793576", + hex"636d63764d6a41774d43397a646d636949485a705a58644362336739496a41674d43", + hex"41784e4451674e7a4969506a787a64486c735a543438495674445245465551567375", + hex"516e747a64484a766132557462476c755a57707661573436636d3931626d52394c6b", + hex"4e37633352796232746c4c5731706447567962476c74615851364d5442394c6b5237", + hex"633352796232746c4c5864705a48526f4f6a4a394c6b56375a6d6c7362446f6a4f57", + hex"4935596a6c686653354765334e30636d39725a5331736157356c593246774f6e4a76", + hex"6457356b66563164506a7776633352356247552b5047636764484a68626e4e6d6233", + hex"4a7450534a74595852796158676f4d5334774d694177494441674d5334774d694134", + hex"4c6a45674d436b69506a78775958526f49475a706247773949694e6d5a6d59694947", + hex"5139496b30784f53417a4d6d677a4e4859794e4567784f586f694c7a34385a79427a", + hex"64484a766132553949694d774d44416949474e7359584e7a50534a4349454d675243", + hex"492b50484268644767675a6d6c7362443069493245314e7a6b7a4f5349675a443069", + hex"545449314944517761446c324d545a6f4c546c364969382b50484268644767675a6d", + hex"6c7362443069497a6b795a444e6d4e5349675a443069545451774944517761446832", + hex"4e3267744f486f694c7a3438634746306143426d615778735053496a5a5745315954", + hex"51334969426b50534a4e4e544d674d7a4a494d546c324c5446734d5459744d545967", + hex"4d5467674d545a364969382b50484268644767675a6d6c7362443069626d39755a53", + hex"49675a4430695454453549444d7961444d30646a49305344453565694976506a7877", + hex"5958526f49475a706247773949694e6c595456684e44636949475139496b30794f53", + hex"41794d5777744e53413164693035614456364969382b5043396e506a77765a7a3438", + hex"5a794230636d467563325a76636d3039496d316864484a70654367754f4451674d43", + hex"4177494334344e4341324e5341314b53492b50484268644767675a44306954546b75", + hex"4e5341794d693435624451754f4341324c6a52684d7934784d69417a4c6a45794944", + hex"41674d4341784c544d674d693479624330304c6a67744e6934305979347a4c544575", + hex"4e4341784c6a59744d69343049444d744d693479656949675a6d6c73624430694932", + hex"517759325a6a5a534976506a78775958526f49475a706247773949694d774d544178", + hex"4d44456949475139496b30304d53343349444d344c6a56734e5334784c5459754e53", + hex"4976506a78775958526f49475139496b30304d693435494449334c6a684d4d546775", + hex"4e4341314f4334784944493049445979624449784c6a67744d6a63754d7941794c6a", + hex"4d744d693434656949675932786863334d39496b55694c7a3438634746306143426d", + hex"615778735053496a4d4445774d5441784969426b50534a4e4e444d754e4341794f53", + hex"347a624330304c6a63674e5334344969382b50484268644767675a44306954545132", + hex"4c6a67674d7a4a6a4d793479494449754e6941344c6a63674d533479494445794c6a", + hex"45744d793479637a4d754e6930354c6a6b754d7930784d693431624330314c6a4567", + hex"4e6934314c5449754f4330754d5330754e7930794c6a63674e5334784c5459754e57", + hex"4d744d7934794c5449754e6930344c6a63744d5334794c5445794c6a45674d793479", + hex"6379307a4c6a59674f5334354c53347a494445794c6a556949474e7359584e7a5053", + hex"4a464969382b50484268644767675a6d6c7362443069493245314e7a6b7a4f534967", + hex"5a443069545449334c6a4d674d6a5a734d5445754f4341784e53343349444d754e43", + hex"41794c6a51674f533478494445304c6a51744d793479494449754d79307849433433", + hex"4c5445774c6a49744d544d754e6930784c6a4d744d7934354c5445784c6a67744d54", + hex"55754e336f694c7a3438634746306143426b50534a4e4d5449674d546b754f577731", + hex"4c6a6b674e793435494445774c6a49744e7934324c544d754e4330304c6a567a4e69", + hex"34344c5455754d5341784d4334334c5451754e574d77494441744e6934324c544d74", + hex"4d544d754d7941784c6a46544d5449674d546b754f5341784d6941784f5334356569", + hex"49675932786863334d39496b55694c7a34385a79426d6157787350534a756232356c", + hex"4969427a64484a766132553949694d774d44416949474e7359584e7a50534a434945", + hex"4d675243492b50484268644767675a44306954545579494455344c6a6c4d4e444175", + hex"4f5341304d7934796243307a4c6a45744d69347a4c5445774c6a59744d5451754e79", + hex"30794c6a6b674d693479494445774c6a59674d5451754e7941784c6a45674d793432", + hex"494445784c6a55674d5455754e58704e4d5449754e5341784f533434624455754f43", + hex"4134494445774c6a4d744e7934304c544d754d7930304c6a5a7a4e6934354c545567", + hex"4d5441754f4330304c6a4e6a4d4341774c5459754e69307a4c6a45744d544d754d79", + hex"3435637930784d43347a494463754e4330784d43347a494463754e4870744c544975", + hex"4e6941794c6a6c734e433433494459754e574d744c6a55674d53347a4c5445754e79", + hex"41794c6a45744d7941794c6a4a734c5451754e7930324c6a566a4c6a4d744d533430", + hex"494445754e6930794c6a51674d7930794c6a4a364969382b50484268644767675a44", + hex"3069545451784c6a4d674d7a67754e5777314c6a45744e6934316253307a4c6a5574", + hex"4d693433624330304c6a59674e533434625467754d53307a4c6a466a4d7934794944", + hex"49754e6941344c6a63674d533479494445794c6a45744d793479637a4d754e693035", + hex"4c6a6b754d7930784d693431624330314c6a45674e6934314c5449754f4330754d53", + hex"30754f4330794c6a63674e5334784c5459754e574d744d7934794c5449754e693034", + hex"4c6a63744d5334794c5445794c6a45674d7934794c544d754e4341304c6a4d744d79", + hex"343249446b754f5330754d7941784d6934314969426a6247467a637a306952694976", + hex"506a78775958526f49475139496b307a4d433434494451304c6a524d4d546b674e54", + hex"67754f57773049444d674d5441744d5449754e7949675932786863334d39496b5969", + hex"4c7a34384c32632b5043396e506a777663335a6e50673d3d227d" + ); /* ","description":"This%20NFT%20can%20be%20redeemed%20on%20HomeWork%20 + to%20grant%20a%20controller%20the%20exclusive%20right%20to%20deploy%20 + contracts%20with%20arbitrary%20bytecode%20to%20the%20designated%20home + %20address.","image":"data:image/svg+xml;charset=utf-8;base64,..."} */ + + // Deploy payload to the runtime storage contract. + _deployRuntimeStorageContract(key, code); + } + + /** + * @notice Perform phase three of the deployment and disable this contract. + * @param key bytes32 The salt to provide to create2. + */ + function phaseThree(bytes32 key) external onlyUntilDisabled { + // Use metamorphic initialization code to deploy contract to home address. + _deployToHomeAddress(key); + + // Disable this contract from here on out - use HomeWork itself instead. + _disabled = true; + } + + /** + * @notice View function used by the metamorphic initialization code when + * deploying a contract to a home address. It returns the address of the + * runtime storage contract that holds the contract creation code, which the + * metamorphic creation code then `DELEGATECALL`s into in order to set up the + * contract and deploy the target runtime code. + * @return The current runtime storage contract that contains the target + * contract creation code. + * @dev This method is not meant to be part of the user-facing contract API, + * but is rather a mechanism for enabling the deployment of arbitrary code via + * fixed initialization code. The odd naming is chosen so that function + * selector will be 0x00000009 - that way, the metamorphic contract can simply + * use the `PC` opcode in order to push the selector to the stack. + */ + function getInitializationCodeFromContractRuntime_6CLUNS() + external + view + returns (address initializationRuntimeStorageContract) + { + // Return address of contract with initialization code set as runtime code. + initializationRuntimeStorageContract = _initializationRuntimeStorageContract; + } + + /** + * @notice Internal function for deploying a runtime storage contract given a + * particular payload. + * @dev To take the provided code payload and deploy a contract with that + * payload as its runtime code, use the following prelude: + * + * 0x600b5981380380925939f3... + * + * 00 60 push1 0b [11 -> offset] + * 02 59 msize [offset, 0] + * 03 81 dup2 [offset, 0, offset] + * 04 38 codesize [offset, 0, offset, codesize] + * 05 03 sub [offset, 0, codesize - offset] + * 06 80 dup1 [offset, 0, codesize - offset, codesize - offset] + * 07 92 swap3 [codesize - offset, 0, codesize - offset, offset] + * 08 59 msize [codesize - offset, 0, codesize - offset, offset, 0] + * 09 39 codecopy [codesize - offset, 0] + * 10 f3 return [] *init_code_in_runtime* + * ... init_code + */ + function _deployRuntimeStorageContract(bytes32 key, bytes memory payload) + internal + returns (address runtimeStorageContract) + { + // Construct the contract creation code using the prelude and the payload. + bytes memory runtimeStorageContractCreationCode = abi.encodePacked( + _ARBITRARY_RUNTIME_PRELUDE, + payload + ); + + assembly { + // Get the location and length of the newly-constructed creation code. + let encoded_data := add(0x20, runtimeStorageContractCreationCode) + let encoded_size := mload(runtimeStorageContractCreationCode) + + // Deploy the runtime storage contract via `CREATE2`. + runtimeStorageContract := create2(0, encoded_data, encoded_size, key) + + // Pass along revert message if the contract did not deploy successfully. + if iszero(runtimeStorageContract) { + returndatacopy(0, 0, returndatasize) + revert(0, returndatasize) + } + } + + // Emit an event with address of newly-deployed runtime storage contract. + emit StorageContractDeployment(runtimeStorageContract); + } + + /** + * @notice Internal function for deploying arbitrary contract code to the home + * address corresponding to a suppied key via metamorphic initialization code. + * @dev This deployment method uses the "metamorphic delegator" pattern, where + * it will retrieve the address of the contract that contains the target + * initialization code, then delegatecall into it, 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] + * 12 fa STATICCALL [0, 0, 1 => success] + * 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] + * 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 { + // Declare a variable for the home address. + address homeAddress; + + 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) + } + } + + // Clear the address of the runtime storage contract from storage. + delete _initializationRuntimeStorageContract; + + // Emit an event with home address and key for the newly-deployed contract. + emit HomeWorkDeployment(homeAddress, key); + } + + /** + * @notice Modifier to disable the contract once deployment is complete. + */ + modifier onlyUntilDisabled() { + require(!_disabled, "Contract is disabled."); + _; + } +} \ No newline at end of file diff --git a/mix.lock b/mix.lock index 5bcd8cacf1..258c71f03f 100644 --- a/mix.lock +++ b/mix.lock @@ -11,6 +11,7 @@ "benchee": {:hex, :benchee, "0.13.2", "30cd4ff5f593fdd218a9b26f3c24d580274f297d88ad43383afe525b1543b165", [:mix], [{:deep_merge, "~> 0.1", [hex: :deep_merge, repo: "hexpm", optional: false]}], "hexpm"}, "benchee_csv": {:hex, :benchee_csv, "0.8.0", "0ca094677d6e2b2f601b7ee7864b754789ef9d24d079432e5e3d6f4fb83a4d80", [:mix], [{:benchee, "~> 0.12", [hex: :benchee, optional: false]}, {:csv, "~> 2.0", [hex: :csv, optional: false]}]}, "binary": {:hex, :binary, "0.0.5", "20d816f7274ea34f1b673b4cff2fdb9ebec9391a7a68c349070d515c66b1b2cf", [:mix], []}, + "briefly": {:git, "https://github.com/CargoSense/briefly.git", "2526e9674a4e6996137e066a1295ea60962712b8", []}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], []}, "bypass": {:hex, :bypass, "1.0.0", "b78b3dcb832a71aca5259c1a704b2e14b55fd4e1327ff942598b4e7d1a7ad83d", [:mix], [{:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: false]}], "hexpm"}, "certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"}, From e15142f82b7e7b46040d0ca9cdcd8883695d0ede Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Thu, 20 Jun 2019 15:57:08 +0300 Subject: [PATCH 02/15] add CHANGELOG entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed9ca13594..2629a797b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ - [#2173](https://github.com/poanetwork/blockscout/pull/2173) - handle correctly empty transactions - [#2174](https://github.com/poanetwork/blockscout/pull/2174) - fix reward channel joining - [#2186](https://github.com/poanetwork/blockscout/pull/2186) - fix net version test +- [#2204](https://github.com/poanetwork/blockscout/pull/2204) - fix large contract verification ### Chore - [#2127](https://github.com/poanetwork/blockscout/pull/2127) - use previouse chromedriver version From 262b59abcee564a53492d256d031a4b955bdeab4 Mon Sep 17 00:00:00 2001 From: zachdaniel Date: Tue, 18 Jun 2019 11:08:33 -0400 Subject: [PATCH 03/15] feat: add BLOCKSCOUT_HOST, and use it in API docs --- CHANGELOG.md | 1 + apps/block_scout_web/config/config.exs | 2 +- .../lib/block_scout_web/templates/api_docs/eth_rpc.html.eex | 2 +- .../lib/block_scout_web/templates/api_docs/index.html.eex | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b4e62d4751..4d440f38ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - [#2075](https://github.com/poanetwork/blockscout/pull/2075) - add blocks cache - [#2151](https://github.com/poanetwork/blockscout/pull/2151) - hide dropdown menu then other networks list is empty - [#2146](https://github.com/poanetwork/blockscout/pull/2146) - feat: add eth_getLogs rpc endpoint +- [#2193](https://github.com/poanetwork/blockscout/pull/2193) - feat: add BLOCKSCOUT_HOST, and use it in API docs ### Fixes - [#2201](https://github.com/poanetwork/blockscout/pull/2201) - footer columns fix diff --git a/apps/block_scout_web/config/config.exs b/apps/block_scout_web/config/config.exs index e601e299f8..28d142eb5c 100644 --- a/apps/block_scout_web/config/config.exs +++ b/apps/block_scout_web/config/config.exs @@ -41,7 +41,7 @@ config :block_scout_web, BlockScoutWeb.Endpoint, ] ], url: [ - host: "localhost", + host: System.get_env("BLOCKSCOUT_HOST") || "localhost", path: System.get_env("NETWORK_PATH") || "/" ], render_errors: [view: BlockScoutWeb.ErrorView, accepts: ~w(html json)], diff --git a/apps/block_scout_web/lib/block_scout_web/templates/api_docs/eth_rpc.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/api_docs/eth_rpc.html.eex index f85620681a..3a0338f4a3 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/api_docs/eth_rpc.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/api_docs/eth_rpc.html.eex @@ -2,7 +2,7 @@

<%= gettext("ETH RPC API Documentation") %>

-

[ <%= gettext "Base URL:" %> <%= @conn.host %>/api/eth_rpc ]

+

[ <%= gettext "Base URL:" %> <%= BlockScoutWeb.Endpoint.url() %>/api/eth_rpc ]

<%= gettext "This API is provided to support some rpc methods in the exact format specified for ethereum nodes, which can be found " %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/api_docs/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/api_docs/index.html.eex index c665fb88e6..0f97b67d45 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/api_docs/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/api_docs/index.html.eex @@ -2,7 +2,7 @@

<%= gettext("API Documentation") %>

-

[ <%= gettext "Base URL:" %> <%= @conn.host %>/api ]

+

[ <%= gettext "Base URL:" %> <%= BlockScoutWeb.Endpoint.url() %>/api ]

<%= gettext "This API is provided for developers transitioning their applications from Etherscan to BlockScout. It supports GET and POST requests." %>

From 4f8dceed1cb09f87e753554e023424f4edaf560a Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Mon, 24 Jun 2019 10:41:45 +0300 Subject: [PATCH 04/15] fix metadata decomdiing in smart contract verification --- apps/explorer/lib/explorer/smart_contract/verifier.ex | 6 ++++++ .../smart_contract/verifier/constructor_arguments.ex | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/apps/explorer/lib/explorer/smart_contract/verifier.ex b/apps/explorer/lib/explorer/smart_contract/verifier.ex index 2dd6545010..ea25d24818 100644 --- a/apps/explorer/lib/explorer/smart_contract/verifier.ex +++ b/apps/explorer/lib/explorer/smart_contract/verifier.ex @@ -107,6 +107,12 @@ defmodule Explorer.SmartContract.Verifier do |> Enum.reverse() |> :binary.list_to_bin() + # Solidity >= 0.5.9; https://github.com/ethereum/solidity/blob/aa4ee3a1559ebc0354926af962efb3fcc7dc15bd/docs/metadata.rst + "a265627a7a72305820" <> <<_::binary-size(64)>> <> "64736f6c6343" <> <<_::binary-size(6)>> <> "0032" <> _constructor_arguments -> + extracted + |> Enum.reverse() + |> :binary.list_to_bin() + <> <> rest -> do_extract_bytecode([next | extracted], rest) end diff --git a/apps/explorer/lib/explorer/smart_contract/verifier/constructor_arguments.ex b/apps/explorer/lib/explorer/smart_contract/verifier/constructor_arguments.ex index aa32d85bb6..de888c0f97 100644 --- a/apps/explorer/lib/explorer/smart_contract/verifier/constructor_arguments.ex +++ b/apps/explorer/lib/explorer/smart_contract/verifier/constructor_arguments.ex @@ -21,6 +21,11 @@ defmodule Explorer.SmartContract.Verifier.ConstructorArguments do constructor_arguments end + # Solidity >= 0.5.9; https://github.com/ethereum/solidity/blob/aa4ee3a1559ebc0354926af962efb3fcc7dc15bd/docs/metadata.rst + defp extract_contrstructor_arguments("a265627a7a72305820" <> <<_::binary-size(64)>> <> "64736f6c6343" <> <<_::binary-size(6)>> <> "0032" <> constructor_arguments) do + constructor_arguments + end + defp extract_constructor_arguments(<<_::binary-size(2)>> <> rest) do extract_constructor_arguments(rest) end From 680c28866c3dc90765e23c27ca60ab97915da0a8 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Mon, 24 Jun 2019 11:27:02 +0300 Subject: [PATCH 05/15] fix typo --- .../smart_contract/verifier/constructor_arguments.ex | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/explorer/lib/explorer/smart_contract/verifier/constructor_arguments.ex b/apps/explorer/lib/explorer/smart_contract/verifier/constructor_arguments.ex index de888c0f97..d6d1572f10 100644 --- a/apps/explorer/lib/explorer/smart_contract/verifier/constructor_arguments.ex +++ b/apps/explorer/lib/explorer/smart_contract/verifier/constructor_arguments.ex @@ -22,7 +22,10 @@ defmodule Explorer.SmartContract.Verifier.ConstructorArguments do end # Solidity >= 0.5.9; https://github.com/ethereum/solidity/blob/aa4ee3a1559ebc0354926af962efb3fcc7dc15bd/docs/metadata.rst - defp extract_contrstructor_arguments("a265627a7a72305820" <> <<_::binary-size(64)>> <> "64736f6c6343" <> <<_::binary-size(6)>> <> "0032" <> constructor_arguments) do + defp extract_constructor_arguments( + "a265627a7a72305820" <> + <<_::binary-size(64)>> <> "64736f6c6343" <> <<_::binary-size(6)>> <> "0032" <> constructor_arguments + ) do constructor_arguments end From 74e969a45d4318c6efb9a70b5acaf65d5ae6c18a Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Mon, 24 Jun 2019 11:51:15 +0300 Subject: [PATCH 06/15] add regression test --- .../lib/explorer/smart_contract/verifier.ex | 3 +- .../explorer/smart_contract/verifier_test.exs | 32 ++++++++ .../solidity_0.5.9_smart_contract.sol | 76 +++++++++++++++++++ 3 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 apps/explorer/test/support/fixture/smart_contract/solidity_0.5.9_smart_contract.sol diff --git a/apps/explorer/lib/explorer/smart_contract/verifier.ex b/apps/explorer/lib/explorer/smart_contract/verifier.ex index ea25d24818..f4e486fd50 100644 --- a/apps/explorer/lib/explorer/smart_contract/verifier.ex +++ b/apps/explorer/lib/explorer/smart_contract/verifier.ex @@ -108,7 +108,8 @@ defmodule Explorer.SmartContract.Verifier do |> :binary.list_to_bin() # Solidity >= 0.5.9; https://github.com/ethereum/solidity/blob/aa4ee3a1559ebc0354926af962efb3fcc7dc15bd/docs/metadata.rst - "a265627a7a72305820" <> <<_::binary-size(64)>> <> "64736f6c6343" <> <<_::binary-size(6)>> <> "0032" <> _constructor_arguments -> + "a265627a7a72305820" <> + <<_::binary-size(64)>> <> "64736f6c6343" <> <<_::binary-size(6)>> <> "0032" <> _constructor_arguments -> extracted |> Enum.reverse() |> :binary.list_to_bin() diff --git a/apps/explorer/test/explorer/smart_contract/verifier_test.exs b/apps/explorer/test/explorer/smart_contract/verifier_test.exs index ef635c2573..9633804c60 100644 --- a/apps/explorer/test/explorer/smart_contract/verifier_test.exs +++ b/apps/explorer/test/explorer/smart_contract/verifier_test.exs @@ -117,6 +117,38 @@ defmodule Explorer.SmartContract.VerifierTest do assert abi != nil end + test "verifies smart contract compiled with Solidity 0.5.9 (includes new metadata in bytecode) with constructor args" do + path = File.cwd!() <> "/test/support/fixture/smart_contract/solidity_0.5.9_smart_contract.sol" + contract = File.read!(path) + + constructor_arguments = + "00000000000000000000000000000000000000000000003635c9adc5dea000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000a54657374546f6b656e32000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006546f6b656e320000000000000000000000000000000000000000000000000000" + + bytecode = + "0x608060405234801561001057600080fd5b50600436106100a95760003560e01c80633177029f116100715780633177029f1461025f57806354fd4d50146102c557806370a082311461034857806395d89b41146103a0578063a9059cbb14610423578063dd62ed3e14610489576100a9565b806306fdde03146100ae578063095ea7b31461013157806318160ddd1461019757806323b872dd146101b5578063313ce5671461023b575b600080fd5b6100b6610501565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156100f65780820151818401526020810190506100db565b50505050905090810190601f1680156101235780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b61017d6004803603604081101561014757600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919050505061059f565b604051808215151515815260200191505060405180910390f35b61019f610691565b6040518082815260200191505060405180910390f35b610221600480360360608110156101cb57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610696565b604051808215151515815260200191505060405180910390f35b61024361090f565b604051808260ff1660ff16815260200191505060405180910390f35b6102ab6004803603604081101561027557600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610922565b604051808215151515815260200191505060405180910390f35b6102cd610a14565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561030d5780820151818401526020810190506102f2565b50505050905090810190601f16801561033a5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b61038a6004803603602081101561035e57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610ab2565b6040518082815260200191505060405180910390f35b6103a8610afa565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156103e85780820151818401526020810190506103cd565b50505050905090810190601f1680156104155780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b61046f6004803603604081101561043957600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610b98565b604051808215151515815260200191505060405180910390f35b6104eb6004803603604081101561049f57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610cfe565b6040518082815260200191505060405180910390f35b60038054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156105975780601f1061056c57610100808354040283529160200191610597565b820191906000526020600020905b81548152906001019060200180831161057a57829003601f168201915b505050505081565b600081600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040518082815260200191505060405180910390a36001905092915050565b600090565b6000816000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205410158015610762575081600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205410155b801561076e5750600082115b1561090357816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540192505081905550816000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254039250508190555081600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040518082815260200191505060405180910390a360019050610908565b600090505b9392505050565b600460009054906101000a900460ff1681565b600081600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040518082815260200191505060405180910390a36001905092915050565b60068054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610aaa5780601f10610a7f57610100808354040283529160200191610aaa565b820191906000526020600020905b815481529060010190602001808311610a8d57829003601f168201915b505050505081565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b60058054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610b905780601f10610b6557610100808354040283529160200191610b90565b820191906000526020600020905b815481529060010190602001808311610b7357829003601f168201915b505050505081565b6000816000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205410158015610be85750600082115b15610cf357816000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540392505081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040518082815260200191505060405180910390a360019050610cf8565b600090505b92915050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490509291505056fea265627a7a72305820fe0ba5210ac95870683c2cb054304b04565703bd16c7d7e956df694c9643c6d264736f6c63430005090032" + + contract_address = insert(:contract_address, contract_code: bytecode) + + :transaction + |> insert( + created_contract_address_hash: contract_address.hash, + input: bytecode <> constructor_arguments + ) + |> with_block() + + params = %{ + "contract_source_code" => contract, + "compiler_version" => "v0.5.9+commit.e560f70d", + "evm_version" => "petersburg", + "name" => "TestToken", + "optimization" => false, + "constructor_arguments" => constructor_arguments + } + + assert {:ok, %{abi: abi}} = Verifier.evaluate_authenticity(contract_address.hash, params) + assert abi != nil + end + test "returns error when bytecode doesn't match", %{contract_code_info: contract_code_info} do contract_address = insert(:contract_address, contract_code: contract_code_info.bytecode) diff --git a/apps/explorer/test/support/fixture/smart_contract/solidity_0.5.9_smart_contract.sol b/apps/explorer/test/support/fixture/smart_contract/solidity_0.5.9_smart_contract.sol new file mode 100644 index 0000000000..25601a98b6 --- /dev/null +++ b/apps/explorer/test/support/fixture/smart_contract/solidity_0.5.9_smart_contract.sol @@ -0,0 +1,76 @@ +pragma solidity ^0.5.9; +contract Token { + function totalSupply() public view returns (uint256 supply) {} + function balanceOf(address _owner) public view returns (uint256 balance) {} + function transfer(address _to, uint256 _value) public returns (bool success) {} + function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {} + function approve(address _spender, uint256 _value) public returns (bool success) {} + function allowance(address _owner, address _spender) public view returns (uint256 remaining) {} + event Transfer(address indexed _from, address indexed _to, uint256 _value); + event Approval(address indexed _owner, address indexed _spender, uint256 _value); +} + + +contract StandardToken is Token { + function transfer(address _to, uint256 _value) public returns (bool success) { + //if (balances[msg.sender] >= _value && balances[_to] + _value > balances[_to]) { + if (balances[msg.sender] >= _value && _value > 0) { + balances[msg.sender] -= _value; + balances[_to] += _value; + emit Transfer(msg.sender, _to, _value); + return true; + } else { return false; } + } + function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) { + //if (balances[_from] >= _value && allowed[_from][msg.sender] >= _value && balances[_to] + _value > balances[_to]) { + if (balances[_from] >= _value && allowed[_from][msg.sender] >= _value && _value > 0) { + balances[_to] += _value; + balances[_from] -= _value; + allowed[_from][msg.sender] -= _value; + emit Transfer(_from, _to, _value); + return true; + } else { return false; } + } + function balanceOf(address _owner) public view returns (uint256 balance) { + return balances[_owner]; + } + function approve(address _spender, uint256 _value) public returns (bool success) { + allowed[msg.sender][_spender] = _value; + emit Approval(msg.sender, _spender, _value); + return true; + } + function allowance(address _owner, address _spender) public view returns (uint256 remaining) { + return allowed[_owner][_spender]; + } + mapping (address => uint256) balances; + mapping (address => mapping (address => uint256)) allowed; + uint256 totalTokenSupply; +} + +contract TestToken is StandardToken { + + /* Public variables */ + string public name; + uint8 public decimals; + string public symbol; + string public version = '0.1'; + + constructor( + uint256 _initialAmount, + string memory _tokenName, + uint8 _decimalUnits, + string memory _tokenSymbol + ) public { + balances[msg.sender] = _initialAmount; + totalTokenSupply = _initialAmount; + name = _tokenName; + decimals = _decimalUnits; + symbol = _tokenSymbol; + } + + function approveAndCall(address _spender, uint256 _value) public returns (bool success) { + allowed[msg.sender][_spender] = _value; + emit Approval(msg.sender, _spender, _value); + return true; + } +} \ No newline at end of file From 402775c457a292f567569e902c6d92ddc018a824 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Mon, 24 Jun 2019 11:54:09 +0300 Subject: [PATCH 07/15] add CHANGELOG entry --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b4e62d4751..a59abeecac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,7 +42,8 @@ - [#2173](https://github.com/poanetwork/blockscout/pull/2173) - handle correctly empty transactions - [#2174](https://github.com/poanetwork/blockscout/pull/2174) - fix reward channel joining - [#2186](https://github.com/poanetwork/blockscout/pull/2186) - fix net version test -- [#2167](https://github.com/poanetwork/blockscout/pull/2168) - feat: document eth rpc api mimicking endpoints +- [#2167](https://github.com/poanetwork/blockscout/pull/2168) - feat: document eth rpc api mimicking endpoints +- [#2225](https://github.com/poanetwork/blockscout/pull/2225) - fix metadata decoding in Solidity 0.5.9 smart contract verification ### Chore - [#2127](https://github.com/poanetwork/blockscout/pull/2127) - use previouse chromedriver version From 0a255264f66735a984c38878e9227c1d08657fe7 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Mon, 24 Jun 2019 16:19:12 +0300 Subject: [PATCH 08/15] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fbda34c9c..fdbf8b678f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,7 +42,7 @@ - [#2173](https://github.com/poanetwork/blockscout/pull/2173) - handle correctly empty transactions - [#2174](https://github.com/poanetwork/blockscout/pull/2174) - fix reward channel joining - [#2186](https://github.com/poanetwork/blockscout/pull/2186) - fix net version test -- [#2167](https://github.com/poanetwork/blockscout/pull/2168) - feat: document eth rpc api mimicking endpoints +- [#2167](https://github.com/poanetwork/blockscout/pull/2167) - feat: document eth rpc api mimicking endpoints - [#2225](https://github.com/poanetwork/blockscout/pull/2225) - fix metadata decoding in Solidity 0.5.9 smart contract verification ### Chore From bd94b103418e4b1d253c9e44d76c67257874d32b Mon Sep 17 00:00:00 2001 From: maxgrapps Date: Tue, 25 Jun 2019 13:37:08 +0300 Subject: [PATCH 09/15] header content alignment issue --- .../assets/css/components/_navbar.scss | 35 +++++++++++++++++-- .../templates/layout/_topnav.html.eex | 4 +-- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/apps/block_scout_web/assets/css/components/_navbar.scss b/apps/block_scout_web/assets/css/components/_navbar.scss index 7f0b81f803..370204e1b8 100644 --- a/apps/block_scout_web/assets/css/components/_navbar.scss +++ b/apps/block_scout_web/assets/css/components/_navbar.scss @@ -22,7 +22,7 @@ $navbar-logo-width: auto !default; font-size: 14px; } .navbar-nav { - flex-grow: 1; + // flex-grow: 1; .nav-link { align-items: center; @@ -153,6 +153,18 @@ $navbar-logo-width: auto !default; @include media-breakpoint-up(xl) { width: 280px; } + @media (min-width: 1366px) { + width: 330px; + } + @media (min-width: 1440px) { + width: 380px; + } + @media (min-width: 1580px) { + width: 430px; + } + @media (min-width: 1800px) { + width: 520px; + } } .input-group-append { height: 38px; @@ -198,7 +210,7 @@ $navbar-logo-width: auto !default; width: 100%; .awesomplete { - @include media-breakpoint-down(sm) { + @include media-breakpoint-down(lg) { width: 100%; } } @@ -210,6 +222,10 @@ $navbar-logo-width: auto !default; .navbar-brand { margin-left: 0; + flex-shrink: 1; + .navbar-logo { + max-width: 100%; + } } .navbar-logo { @@ -227,3 +243,18 @@ $navbar-logo-width: auto !default; .add-border { border: 1px solid transparentize($white, 0.30); } + +.navbar-collapse { + justify-content: flex-end; + align-items: flex-start; + flex-shrink: 0; + @media (min-width: 992px) { + align-items: center; + } +} + +.navbar-container, .navbar-primary { + @include media-breakpoint-up(xl) { + padding-right: 0; + } +} \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex index f2a4e9441e..929cd30ab7 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex @@ -1,5 +1,5 @@