diff --git a/CodingStandards.txt b/CodingStandards.txt index e1a1b3ba9..a98a74c78 100644 --- a/CodingStandards.txt +++ b/CodingStandards.txt @@ -13,7 +13,7 @@ c. Don't use braces for condition-body one-liners. d. Never place condition bodies on same line as condition. e. Space between first paren and keyword, but *not* following first paren or preceeding final paren. f. No spaces when fewer than intra-expression three parens together; when three or more, space according to clarity. -g. No spaces for subscripting. +g. No spaces for subscripting or unary operators. h. No space before ':' but one after it, except in the ternary operator: one on both sides. i. Space all other operators. j. Braces, when used, always have their own lines and are at same indentation level as "parent" scope. diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index 0de7e2cb8..610fd032d 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -1774,6 +1774,7 @@ void Main::on_net_triggered() else { ui->downloadView->setDownloadMan(nullptr); + writeSettings(); web3()->stopNetwork(); } } diff --git a/libdevcore/CommonJS.h b/libdevcore/CommonJS.h index cc487d54c..ade089e16 100644 --- a/libdevcore/CommonJS.h +++ b/libdevcore/CommonJS.h @@ -38,7 +38,10 @@ template std::string toJS(FixedHash const& _h) template std::string toJS(boost::multiprecision::number> const& _n) { - return "0x" + toHex(toCompactBigEndian(_n, 1)); + std::string h = toHex(toCompactBigEndian(_n, 1)); + // remove first 0, if it is necessary; + std::string res = h[0] != '0' ? h : h.substr(1); + return "0x" + res; } inline std::string toJS(bytes const& _n, std::size_t _padding = 0) diff --git a/libdevcrypto/MemoryDB.h b/libdevcrypto/MemoryDB.h index 7d39ba73b..7858126f8 100644 --- a/libdevcrypto/MemoryDB.h +++ b/libdevcrypto/MemoryDB.h @@ -32,8 +32,10 @@ namespace dev { struct DBChannel: public LogChannel { static const char* name() { return "TDB"; } static const int verbosity = 18; }; +struct DBWarn: public LogChannel { static const char* name() { return "TDB"; } static const int verbosity = 1; }; #define dbdebug clog(DBChannel) +#define dbwarn clog(DBWarn) class MemoryDB { diff --git a/libethash/CMakeLists.txt b/libethash/CMakeLists.txt index 38fc821c0..c92240086 100644 --- a/libethash/CMakeLists.txt +++ b/libethash/CMakeLists.txt @@ -12,6 +12,7 @@ endif() set(FILES util.c util.h + io.c internal.c ethash.h endian.h @@ -19,6 +20,12 @@ set(FILES util.c fnv.h data_sizes.h) +if (MSVC) + list(APPEND FILES io_win32.c) +else() + list(APPEND FILES io_posix.c) +endif() + if (NOT CRYPTOPP_FOUND) find_package(CryptoPP 5.6.2) endif() diff --git a/libethash/data_sizes.h b/libethash/data_sizes.h index 3b747b3ea..cf52ae4f8 100644 --- a/libethash/data_sizes.h +++ b/libethash/data_sizes.h @@ -48,7 +48,7 @@ extern "C" { // Sow[i*HashBytes]; j++]]]][[2]][[1]] -static const size_t dag_sizes[2048] = { +static const uint64_t dag_sizes[2048] = { 1073739904U, 1082130304U, 1090514816U, 1098906752U, 1107293056U, 1115684224U, 1124070016U, 1132461952U, 1140849536U, 1149232768U, 1157627776U, 1166013824U, 1174404736U, 1182786944U, 1191180416U, @@ -477,7 +477,7 @@ static const size_t dag_sizes[2048] = { // While[! PrimeQ[i], i--]; // Sow[i*HashBytes]; j++]]]][[2]][[1]] -const size_t cache_sizes[2048] = { +const uint64_t cache_sizes[2048] = { 16776896U, 16907456U, 17039296U, 17170112U, 17301056U, 17432512U, 17563072U, 17693888U, 17824192U, 17955904U, 18087488U, 18218176U, 18349504U, 18481088U, 18611392U, 18742336U, 18874304U, 19004224U, 19135936U, 19267264U, 19398208U, diff --git a/libethash/ethash.h b/libethash/ethash.h index a7159de65..7594fc835 100644 --- a/libethash/ethash.h +++ b/libethash/ethash.h @@ -24,92 +24,115 @@ #include #include #include +#include #include "compiler.h" -#define REVISION 23 -#define DATASET_BYTES_INIT 1073741824U // 2**30 -#define DATASET_BYTES_GROWTH 8388608U // 2**23 -#define CACHE_BYTES_INIT 1073741824U // 2**24 -#define CACHE_BYTES_GROWTH 131072U // 2**17 -#define EPOCH_LENGTH 30000U -#define MIX_BYTES 128 -#define HASH_BYTES 64 -#define DATASET_PARENTS 256 -#define CACHE_ROUNDS 3 -#define ACCESSES 64 +#define ETHASH_REVISION 23 +#define ETHASH_DATASET_BYTES_INIT 1073741824U // 2**30 +#define ETHASH_DATASET_BYTES_GROWTH 8388608U // 2**23 +#define ETHASH_CACHE_BYTES_INIT 1073741824U // 2**24 +#define ETHASH_CACHE_BYTES_GROWTH 131072U // 2**17 +#define ETHASH_EPOCH_LENGTH 30000U +#define ETHASH_MIX_BYTES 128 +#define ETHASH_HASH_BYTES 64 +#define ETHASH_DATASET_PARENTS 256 +#define ETHASH_CACHE_ROUNDS 3 +#define ETHASH_ACCESSES 64 #ifdef __cplusplus extern "C" { #endif typedef struct ethash_params { - size_t full_size; // Size of full data set (in bytes, multiple of mix size (128)). - size_t cache_size; // Size of compute cache (in bytes, multiple of node size (64)). + uint64_t full_size; // Size of full data set (in bytes, multiple of mix size (128)). + uint64_t cache_size; // Size of compute cache (in bytes, multiple of node size (64)). } ethash_params; typedef struct ethash_return_value { - uint8_t result[32]; - uint8_t mix_hash[32]; + uint8_t result[32]; + uint8_t mix_hash[32]; } ethash_return_value; -size_t ethash_get_datasize(const uint32_t block_number); -size_t ethash_get_cachesize(const uint32_t block_number); +uint64_t ethash_get_datasize(const uint32_t block_number); +uint64_t ethash_get_cachesize(const uint32_t block_number); // initialize the parameters static inline void ethash_params_init(ethash_params *params, const uint32_t block_number) { - params->full_size = ethash_get_datasize(block_number); - params->cache_size = ethash_get_cachesize(block_number); + params->full_size = ethash_get_datasize(block_number); + params->cache_size = ethash_get_cachesize(block_number); } -typedef struct ethash_cache { - void *mem; -} ethash_cache; +/*********************************** + * OLD API ************************* + *********************************** + ******************** (deprecated) * + ***********************************/ -void ethash_mkcache(ethash_cache *cache, ethash_params const *params, const uint8_t seed[32]); -void ethash_compute_full_data(void *mem, ethash_params const *params, ethash_cache const *cache); -void ethash_full(ethash_return_value *ret, void const *full_mem, ethash_params const *params, const uint8_t header_hash[32], const uint64_t nonce); -void ethash_light(ethash_return_value *ret, ethash_cache const *cache, ethash_params const *params, const uint8_t header_hash[32], const uint64_t nonce); void ethash_get_seedhash(uint8_t seedhash[32], const uint32_t block_number); +void ethash_mkcache(void *cache, ethash_params const *params, const uint8_t seed[32]); +void ethash_light(ethash_return_value *ret, void const *cache, ethash_params const *params, const uint8_t header_hash[32], const uint64_t nonce); +void ethash_compute_full_data(void *mem, ethash_params const *params, void const *cache); +void ethash_full(ethash_return_value *ret, void const *full_mem, ethash_params const *params, const uint8_t header_hash[32], const uint64_t nonce); -static inline void ethash_prep_light(void *cache, ethash_params const *params, const uint8_t seed[32]) { - ethash_cache c; - c.mem = cache; - ethash_mkcache(&c, params, seed); -} +/*********************************** + * NEW API ************************* + ***********************************/ + +// TODO: compute params and seed in ethash_new_light; it should take only block_number +// TODO: store params in ethash_light_t/ethash_full_t to avoid having to repass into compute/new_full + +typedef uint8_t const ethash_seedhash_t[32]; -static inline void ethash_compute_light(ethash_return_value *ret, void const *cache, ethash_params const *params, const uint8_t header_hash[32], const uint64_t nonce) { - ethash_cache c; - c.mem = (void *) cache; - ethash_light(ret, &c, params, header_hash, nonce); +typedef void const* ethash_light_t; +static inline ethash_light_t ethash_new_light(ethash_params const* params, ethash_seedhash_t seed) { + void* ret = malloc(params->cache_size); + ethash_mkcache(ret, params, seed); + return ret; +} +static inline void ethash_compute_light(ethash_return_value *ret, ethash_light_t light, ethash_params const *params, const uint8_t header_hash[32], const uint64_t nonce) { + ethash_light(ret, light, params, header_hash, nonce); +} +static inline void ethash_delete_light(ethash_light_t light) { + free((void*)light); } +typedef void const* ethash_full_t; +static inline ethash_full_t ethash_new_full(ethash_params const* params, ethash_light_t light) { + void* ret = malloc(params->full_size); + ethash_compute_full_data(ret, params, light); + return ret; +} static inline void ethash_prep_full(void *full, ethash_params const *params, void const *cache) { - ethash_cache c; - c.mem = (void *) cache; - ethash_compute_full_data(full, params, &c); + ethash_compute_full_data(full, params, cache); } - static inline void ethash_compute_full(ethash_return_value *ret, void const *full, ethash_params const *params, const uint8_t header_hash[32], const uint64_t nonce) { - ethash_full(ret, full, params, header_hash, nonce); + ethash_full(ret, full, params, header_hash, nonce); } -// Returns if hash is less than or equal to difficulty -static inline int ethash_check_difficulty( - const uint8_t hash[32], - const uint8_t difficulty[32]) { - // Difficulty is big endian - for (int i = 0; i < 32; i++) { - if (hash[i] == difficulty[i]) continue; - return hash[i] < difficulty[i]; - } - return 1; +/// @brief Compare two s256-bit big-endian values. +/// @returns 1 if @a a is less than or equal to @a b, 0 otherwise. +/// Both parameters are 256-bit big-endian values. +static inline int ethash_leq_be256(const uint8_t a[32], const uint8_t b[32]) { + // Boundary is big endian + for (int i = 0; i < 32; i++) { + if (a[i] == b[i]) + continue; + return a[i] < b[i]; + } + return 1; } -int ethash_quick_check_difficulty( - const uint8_t header_hash[32], - const uint64_t nonce, - const uint8_t mix_hash[32], - const uint8_t difficulty[32]); +/// Perofrms a cursory check on the validity of the nonce. +/// @returns 1 if the nonce may possibly be valid for the given header_hash & boundary. +/// @p boundary equivalent to 2 ^ 256 / block_difficulty, represented as a 256-bit big-endian. +int ethash_preliminary_check_boundary( + const uint8_t header_hash[32], + const uint64_t nonce, + const uint8_t mix_hash[32], + const uint8_t boundary[32]); + +#define ethash_quick_check_difficulty ethash_preliminary_check_boundary +#define ethash_check_difficulty ethash_leq_be256 #ifdef __cplusplus } diff --git a/libethash/internal.c b/libethash/internal.c index 0a7e767e7..130ca13c3 100644 --- a/libethash/internal.c +++ b/libethash/internal.c @@ -37,14 +37,14 @@ #include "sha3.h" #endif // WITH_CRYPTOPP -size_t ethash_get_datasize(const uint32_t block_number) { - assert(block_number / EPOCH_LENGTH < 2048); - return dag_sizes[block_number / EPOCH_LENGTH]; +uint64_t ethash_get_datasize(const uint32_t block_number) { + assert(block_number / ETHASH_EPOCH_LENGTH < 2048); + return dag_sizes[block_number / ETHASH_EPOCH_LENGTH]; } -size_t ethash_get_cachesize(const uint32_t block_number) { - assert(block_number / EPOCH_LENGTH < 2048); - return cache_sizes[block_number / EPOCH_LENGTH]; +uint64_t ethash_get_cachesize(const uint32_t block_number) { + assert(block_number / ETHASH_EPOCH_LENGTH < 2048); + return cache_sizes[block_number / ETHASH_EPOCH_LENGTH]; } // Follows Sergio's "STRICT MEMORY HARD HASHING FUNCTIONS" (2014) @@ -63,7 +63,7 @@ void static ethash_compute_cache_nodes( SHA3_512(nodes[i].bytes, nodes[i - 1].bytes, 64); } - for (unsigned j = 0; j != CACHE_ROUNDS; j++) { + for (unsigned j = 0; j != ETHASH_CACHE_ROUNDS; j++) { for (unsigned i = 0; i != num_nodes; i++) { uint32_t const idx = nodes[i].words[0] % num_nodes; node data; @@ -85,10 +85,10 @@ void static ethash_compute_cache_nodes( } void ethash_mkcache( - ethash_cache *cache, + void *cache, ethash_params const *params, const uint8_t seed[32]) { - node *nodes = (node *) cache->mem; + node *nodes = (node *) cache; ethash_compute_cache_nodes(nodes, params, seed); } @@ -96,10 +96,10 @@ void ethash_calculate_dag_item( node *const ret, const unsigned node_index, const struct ethash_params *params, - const struct ethash_cache *cache) { + const void *cache) { uint32_t num_parent_nodes = (uint32_t) (params->cache_size / sizeof(node)); - node const *cache_nodes = (node const *) cache->mem; + node const *cache_nodes = (node const *) cache; node const *init = &cache_nodes[node_index % num_parent_nodes]; memcpy(ret, init, sizeof(node)); @@ -114,7 +114,7 @@ void ethash_calculate_dag_item( __m128i xmm3 = ret->xmm[3]; #endif - for (unsigned i = 0; i != DATASET_PARENTS; ++i) { + for (unsigned i = 0; i != ETHASH_DATASET_PARENTS; ++i) { uint32_t parent_index = ((node_index ^ i) * FNV_PRIME ^ ret->words[i % NODE_WORDS]) % num_parent_nodes; node const *parent = &cache_nodes[parent_index]; @@ -150,7 +150,7 @@ void ethash_calculate_dag_item( void ethash_compute_full_data( void *mem, ethash_params const *params, - ethash_cache const *cache) { + void const *cache) { assert((params->full_size % (sizeof(uint32_t) * MIX_WORDS)) == 0); assert((params->full_size % sizeof(node)) == 0); node *full_nodes = mem; @@ -164,7 +164,7 @@ void ethash_compute_full_data( static void ethash_hash( ethash_return_value *ret, node const *full_nodes, - ethash_cache const *cache, + void const *cache, ethash_params const *params, const uint8_t header_hash[32], const uint64_t nonce) { @@ -201,7 +201,7 @@ static void ethash_hash( num_full_pages = (unsigned) (params->full_size / page_size); - for (unsigned i = 0; i != ACCESSES; ++i) { + for (unsigned i = 0; i != ETHASH_ACCESSES; ++i) { uint32_t const index = ((s_mix->words[0] ^ i) * FNV_PRIME ^ mix->words[i % MIX_WORDS]) % num_full_pages; for (unsigned n = 0; n != MIX_NODES; ++n) { @@ -275,26 +275,26 @@ void ethash_quick_hash( void ethash_get_seedhash(uint8_t seedhash[32], const uint32_t block_number) { memset(seedhash, 0, 32); - const uint32_t epochs = block_number / EPOCH_LENGTH; + const uint32_t epochs = block_number / ETHASH_EPOCH_LENGTH; for (uint32_t i = 0; i < epochs; ++i) SHA3_256(seedhash, seedhash, 32); } -int ethash_quick_check_difficulty( +int ethash_preliminary_check_boundary( const uint8_t header_hash[32], const uint64_t nonce, const uint8_t mix_hash[32], - const uint8_t difficulty[32]) { + const uint8_t difficulty[32]) { - uint8_t return_hash[32]; + uint8_t return_hash[32]; ethash_quick_hash(return_hash, header_hash, nonce, mix_hash); - return ethash_check_difficulty(return_hash, difficulty); + return ethash_leq_be256(return_hash, difficulty); } void ethash_full(ethash_return_value *ret, void const *full_mem, ethash_params const *params, const uint8_t previous_hash[32], const uint64_t nonce) { ethash_hash(ret, (node const *) full_mem, NULL, params, previous_hash, nonce); } -void ethash_light(ethash_return_value *ret, ethash_cache const *cache, ethash_params const *params, const uint8_t previous_hash[32], const uint64_t nonce) { +void ethash_light(ethash_return_value *ret, void const *cache, ethash_params const *params, const uint8_t previous_hash[32], const uint64_t nonce) { ethash_hash(ret, NULL, cache, params, previous_hash, nonce); -} \ No newline at end of file +} diff --git a/libethash/internal.h b/libethash/internal.h index bcbacdaa4..dec7e6b13 100644 --- a/libethash/internal.h +++ b/libethash/internal.h @@ -3,7 +3,7 @@ #include "endian.h" #include "ethash.h" -#define ENABLE_SSE 1 +#define ENABLE_SSE 0 #if defined(_M_X64) && ENABLE_SSE #include @@ -15,7 +15,7 @@ extern "C" { // compile time settings #define NODE_WORDS (64/4) -#define MIX_WORDS (MIX_BYTES/4) +#define MIX_WORDS (ETHASH_MIX_BYTES/4) #define MIX_NODES (MIX_WORDS / NODE_WORDS) #include @@ -34,7 +34,7 @@ void ethash_calculate_dag_item( node *const ret, const unsigned node_index, ethash_params const *params, - ethash_cache const *cache + void const *cache ); void ethash_quick_hash( @@ -45,4 +45,4 @@ void ethash_quick_hash( #ifdef __cplusplus } -#endif \ No newline at end of file +#endif diff --git a/libethash/io.c b/libethash/io.c new file mode 100644 index 000000000..0e935fa59 --- /dev/null +++ b/libethash/io.c @@ -0,0 +1,89 @@ +/* + This file is part of ethash. + + ethash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + ethash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with ethash. If not, see . +*/ +/** @file io.c + * @author Lefteris Karapetsas + * @date 2015 + */ +#include "io.h" +#include +#include + +// silly macro to save some typing +#define PASS_ARR(c_) (c_), sizeof(c_) + +static bool ethash_io_write_file(char const *dirname, + char const* filename, + size_t filename_length, + void const* data, + size_t data_size) +{ + bool ret = false; + char *fullname = ethash_io_create_filename(dirname, filename, filename_length); + if (!fullname) { + return false; + } + FILE *f = fopen(fullname, "wb"); + if (!f) { + goto free_name; + } + if (data_size != fwrite(data, 1, data_size, f)) { + goto close; + } + + ret = true; +close: + fclose(f); +free_name: + free(fullname); + return ret; +} + +bool ethash_io_write(char const *dirname, + ethash_params const* params, + ethash_blockhash_t seedhash, + void const* cache, + uint8_t **data, + size_t *data_size) +{ + char info_buffer[DAG_MEMO_BYTESIZE]; + // allocate the bytes + uint8_t *temp_data_ptr = malloc(params->full_size); + if (!temp_data_ptr) { + goto end; + } + ethash_compute_full_data(temp_data_ptr, params, cache); + + if (!ethash_io_write_file(dirname, PASS_ARR(DAG_FILE_NAME), temp_data_ptr, params->full_size)) { + goto fail_free; + } + + ethash_io_serialize_info(ETHASH_REVISION, seedhash, info_buffer); + if (!ethash_io_write_file(dirname, PASS_ARR(DAG_MEMO_NAME), info_buffer, DAG_MEMO_BYTESIZE)) { + goto fail_free; + } + + *data = temp_data_ptr; + *data_size = params->full_size; + return true; + +fail_free: + free(temp_data_ptr); +end: + return false; +} + +#undef PASS_ARR diff --git a/libethash/io.h b/libethash/io.h new file mode 100644 index 000000000..0fa292362 --- /dev/null +++ b/libethash/io.h @@ -0,0 +1,116 @@ +/* + This file is part of ethash. + + ethash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + ethash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with ethash. If not, see . +*/ +/** @file io.h + * @author Lefteris Karapetsas + * @date 2015 + */ +#pragma once +#include +#include +#include +#include "ethash.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct ethash_blockhash { uint8_t b[32]; } ethash_blockhash_t; + +static const char DAG_FILE_NAME[] = "full"; +static const char DAG_MEMO_NAME[] = "full.info"; +// MSVC thinks that "static const unsigned int" is not a compile time variable. Sorry for the #define :( +#define DAG_MEMO_BYTESIZE 36 + +/// Possible return values of @see ethash_io_prepare +enum ethash_io_rc { + ETHASH_IO_FAIL = 0, ///< There has been an IO failure + ETHASH_IO_MEMO_MISMATCH, ///< Memo file either did not exist or there was content mismatch + ETHASH_IO_MEMO_MATCH, ///< Memo file existed and contents matched. No need to do anything +}; + +/** + * Prepares io for ethash + * + * Create the DAG directory if it does not exist, and check if the memo file matches. + * If it does not match then it's deleted to pave the way for @ref ethash_io_write() + * + * @param dirname A null terminated c-string of the path of the ethash + * data directory. If it does not exist it's created. + * @param seedhash The seedhash of the current block number + * @return For possible return values @see enum ethash_io_rc + */ +enum ethash_io_rc ethash_io_prepare(char const *dirname, ethash_blockhash_t seedhash); +/** + * Fully computes data and writes it to the file on disk. + * + * This function should be called after @see ethash_io_prepare() and only if + * its return value is @c ETHASH_IO_MEMO_MISMATCH. Will write both the full data + * and the memo file. + * + * @param[in] dirname A null terminated c-string of the path of the ethash + * data directory. Has to exist. + * @param[in] params An ethash_params object containing the full size + * and the cache size + * @param[in] seedhash The seedhash of the current block number + * @param[in] cache The cache data. Would have usually been calulated by + * @see ethash_prep_light(). + * @param[out] data Pass a pointer to uint8_t by reference here. If the + * function is succesfull then this point to the allocated + * data calculated by @see ethash_prep_full(). Memory + * ownership is transfered to the callee. Remember that + * you eventually need to free this with a call to free(). + * @param[out] data_size Pass a size_t by value. If the function is succesfull + * then this will contain the number of bytes allocated + * for @a data. + * @return True for success and false in case of failure. + */ +bool ethash_io_write(char const *dirname, + ethash_params const* params, + ethash_blockhash_t seedhash, + void const* cache, + uint8_t **data, + size_t *data_size); + +static inline void ethash_io_serialize_info(uint32_t revision, + ethash_blockhash_t seed_hash, + char *output) +{ + // if .info is only consumed locally we don't really care about endianess + memcpy(output, &revision, 4); + memcpy(output + 4, &seed_hash, 32); +} + +static inline char *ethash_io_create_filename(char const *dirname, + char const* filename, + size_t filename_length) +{ + // in C the cast is not needed, but a C++ compiler will complain for invalid conversion + char *name = (char*)malloc(strlen(dirname) + filename_length); + if (!name) { + return NULL; + } + + name[0] = '\0'; + strcat(name, dirname); + strcat(name, filename); + return name; +} + + +#ifdef __cplusplus +} +#endif diff --git a/libethash/io_posix.c b/libethash/io_posix.c new file mode 100644 index 000000000..9d9ccac69 --- /dev/null +++ b/libethash/io_posix.c @@ -0,0 +1,76 @@ +/* + This file is part of ethash. + + ethash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + ethash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with ethash. If not, see . +*/ +/** @file io_posix.c + * @author Lefteris Karapetsas + * @date 2015 + */ + +#include "io.h" +#include +#include +#include +#include +#include +#include + +enum ethash_io_rc ethash_io_prepare(char const *dirname, ethash_blockhash_t seedhash) +{ + char read_buffer[DAG_MEMO_BYTESIZE]; + char expect_buffer[DAG_MEMO_BYTESIZE]; + enum ethash_io_rc ret = ETHASH_IO_FAIL; + + // assert directory exists, full owner permissions and read/search for others + int rc = mkdir(dirname, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); + if (rc == -1 && errno != EEXIST) { + goto end; + } + + char *memofile = ethash_io_create_filename(dirname, DAG_MEMO_NAME, sizeof(DAG_MEMO_NAME)); + if (!memofile) { + goto end; + } + + // try to open memo file + FILE *f = fopen(memofile, "rb"); + if (!f) { + // file does not exist, so no checking happens. All is fine. + ret = ETHASH_IO_MEMO_MISMATCH; + goto free_memo; + } + + if (fread(read_buffer, 1, DAG_MEMO_BYTESIZE, f) != DAG_MEMO_BYTESIZE) { + goto close; + } + + ethash_io_serialize_info(ETHASH_REVISION, seedhash, expect_buffer); + if (memcmp(read_buffer, expect_buffer, DAG_MEMO_BYTESIZE) != 0) { + // we have different memo contents so delete the memo file + if (unlink(memofile) != 0) { + goto close; + } + ret = ETHASH_IO_MEMO_MISMATCH; + } + + ret = ETHASH_IO_MEMO_MATCH; + +close: + fclose(f); +free_memo: + free(memofile); +end: + return ret; +} diff --git a/libethash/io_win32.c b/libethash/io_win32.c new file mode 100644 index 000000000..77367fdd9 --- /dev/null +++ b/libethash/io_win32.c @@ -0,0 +1,73 @@ +/* + This file is part of ethash. + + ethash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + ethash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with ethash. If not, see . +*/ +/** @file io_win32.c + * @author Lefteris Karapetsas + * @date 2015 + */ + +#include "io.h" +#include +#include +#include + +enum ethash_io_rc ethash_io_prepare(char const *dirname, ethash_blockhash_t seedhash) +{ + char read_buffer[DAG_MEMO_BYTESIZE]; + char expect_buffer[DAG_MEMO_BYTESIZE]; + enum ethash_io_rc ret = ETHASH_IO_FAIL; + + // assert directory exists + int rc = _mkdir(dirname); + if (rc == -1 && errno != EEXIST) { + goto end; + } + + char *memofile = ethash_io_create_filename(dirname, DAG_MEMO_NAME, sizeof(DAG_MEMO_NAME)); + if (!memofile) { + goto end; + } + + // try to open memo file + FILE *f = fopen(memofile, "rb"); + if (!f) { + // file does not exist, so no checking happens. All is fine. + ret = ETHASH_IO_MEMO_MISMATCH; + goto free_memo; + } + + if (fread(read_buffer, 1, DAG_MEMO_BYTESIZE, f) != DAG_MEMO_BYTESIZE) { + goto close; + } + + ethash_io_serialize_info(ETHASH_REVISION, seedhash, expect_buffer); + if (memcmp(read_buffer, expect_buffer, DAG_MEMO_BYTESIZE) != 0) { + // we have different memo contents so delete the memo file + if (_unlink(memofile) != 0) { + goto close; + } + ret = ETHASH_IO_MEMO_MISMATCH; + } + + ret = ETHASH_IO_MEMO_MATCH; + +close: + fclose(f); +free_memo: + free(memofile); +end: + return ret; +} diff --git a/libethcore/Common.cpp b/libethcore/Common.cpp index d3ecbcc5f..ef4da1dd7 100644 --- a/libethcore/Common.cpp +++ b/libethcore/Common.cpp @@ -34,7 +34,7 @@ namespace eth { const unsigned c_ethashVersion = c_ethashRevision; -const unsigned c_protocolVersion = 59; +const unsigned c_protocolVersion = 60; const unsigned c_databaseBaseVersion = 8; #if ETH_FATDB const unsigned c_databaseVersionModifier = 1; diff --git a/libethcore/Ethasher.cpp b/libethcore/Ethasher.cpp index 880bc139a..64db69187 100644 --- a/libethcore/Ethasher.cpp +++ b/libethcore/Ethasher.cpp @@ -41,7 +41,23 @@ using namespace eth; Ethasher* dev::eth::Ethasher::s_this = nullptr; -bytes const& Ethasher::cache(BlockInfo const& _header) +Ethasher::~Ethasher() +{ + while (!m_caches.empty()) + killCache(m_caches.begin()->first); +} + +void Ethasher::killCache(h256 const& _s) +{ + RecursiveGuard l(x_this); + if (m_caches.count(_s)) + { + ethash_delete_light(m_caches.at(_s)); + m_caches.erase(_s); + } +} + +void const* Ethasher::cache(BlockInfo const& _header) { RecursiveGuard l(x_this); if (_header.number > c_ethashEpochLength * 2048) @@ -54,8 +70,7 @@ bytes const& Ethasher::cache(BlockInfo const& _header) if (!m_caches.count(_header.seedHash())) { ethash_params p = params((unsigned)_header.number); - m_caches[_header.seedHash()].resize(p.cache_size); - ethash_prep_light(m_caches[_header.seedHash()].data(), &p, _header.seedHash().data()); + m_caches[_header.seedHash()] = ethash_new_light(&p, _header.seedHash().data()); } return m_caches[_header.seedHash()]; } @@ -84,7 +99,7 @@ bytesConstRef Ethasher::full(BlockInfo const& _header) ethash_params p = params((unsigned)_header.number); m_fulls[_header.seedHash()] = bytesRef(new byte[p.full_size], p.full_size); auto c = cache(_header); - ethash_prep_full(m_fulls[_header.seedHash()].data(), &p, c.data()); + ethash_prep_full(m_fulls[_header.seedHash()].data(), &p, c); writeFile(memoFile, m_fulls[_header.seedHash()]); writeFile(memoFile + ".info", info); } @@ -147,7 +162,7 @@ Ethasher::Result Ethasher::eval(BlockInfo const& _header, Nonce const& _nonce) { auto p = Ethasher::params(_header); ethash_return_value r; - ethash_compute_light(&r, Ethasher::get()->cache(_header).data(), &p, _header.headerHash(WithoutNonce).data(), (uint64_t)(u64)_nonce); + ethash_compute_light(&r, Ethasher::get()->cache(_header), &p, _header.headerHash(WithoutNonce).data(), (uint64_t)(u64)_nonce); // cdebug << "Ethasher::eval sha3(cache):" << sha3(Ethasher::get()->cache(_header)) << "hh:" << _header.headerHash(WithoutNonce) << "nonce:" << _nonce << " => " << h256(r.result, h256::ConstructFromPointer); return Result{h256(r.result, h256::ConstructFromPointer), h256(r.mix_hash, h256::ConstructFromPointer)}; } diff --git a/libethcore/Ethasher.h b/libethcore/Ethasher.h index c160d38da..a10c206d1 100644 --- a/libethcore/Ethasher.h +++ b/libethcore/Ethasher.h @@ -30,21 +30,8 @@ #include #include #include // TODO: REMOVE once everything merged into this class and an opaque API can be provided. -static const unsigned c_ethashRevision = REVISION; -static const unsigned c_ethashEpochLength = EPOCH_LENGTH; -#undef REVISION -#undef DATASET_BYTES_INIT -#undef DATASET_BYTES_GROWTH -#undef CACHE_BYTES_INIT -#undef CACHE_BYTES_GROWTH -#undef DAGSIZE_BYTES_INIT -#undef DAG_GROWTH -#undef EPOCH_LENGTH -#undef MIX_BYTES -#undef HASH_BYTES -#undef DATASET_PARENTS -#undef CACHE_ROUNDS -#undef ACCESSES +static const unsigned c_ethashRevision = ETHASH_REVISION; +static const unsigned c_ethashEpochLength = ETHASH_EPOCH_LENGTH; #include "Common.h" #include "BlockInfo.h" @@ -57,10 +44,14 @@ class Ethasher { public: Ethasher() {} + ~Ethasher(); static Ethasher* get() { if (!s_this) s_this = new Ethasher(); return s_this; } - bytes const& cache(BlockInfo const& _header); + using LightType = void const*; + using FullType = void const*; + + LightType cache(BlockInfo const& _header); bytesConstRef full(BlockInfo const& _header); static ethash_params params(BlockInfo const& _header); static ethash_params params(unsigned _n); @@ -104,9 +95,11 @@ public: }; private: + void killCache(h256 const& _s); + static Ethasher* s_this; RecursiveMutex x_this; - std::map m_caches; + std::map m_caches; std::map m_fulls; }; diff --git a/libethereum/BlockChain.cpp b/libethereum/BlockChain.cpp index 508531f9d..8c0bd2b8b 100644 --- a/libethereum/BlockChain.cpp +++ b/libethereum/BlockChain.cpp @@ -339,6 +339,13 @@ h256s BlockChain::import(bytes const& _block, OverlayDB const& _db) #endif // All ok - insert into DB { + // ensure parent is cached for later addition. + // TODO: this is a bit horrible would be better refactored into an enveloping UpgradableGuard + // together with an "ensureCachedWithUpdatableLock(l)" method. + // This is safe in practice since the caches don't get flushed nearly often enough to be + // done here. + details(bi.parentHash); + WriteGuard l(x_details); m_details[newHash] = BlockDetails((unsigned)pd.number + 1, td, bi.parentHash, {}); m_details[bi.parentHash].children.push_back(newHash); @@ -455,14 +462,14 @@ h256s BlockChain::import(bytes const& _block, OverlayDB const& _db) h256s BlockChain::treeRoute(h256 const& _from, h256 const& _to, h256* o_common, bool _pre, bool _post) const { - // cdebug << "treeRoute" << _from.abridged() << "..." << _to.abridged(); + cdebug << "treeRoute" << _from.abridged() << "..." << _to.abridged(); if (!_from || !_to) return h256s(); h256s ret; h256s back; unsigned fn = details(_from).number; unsigned tn = details(_to).number; - // cdebug << "treeRoute" << fn << "..." << tn; + cdebug << "treeRoute" << fn << "..." << tn; h256 from = _from; while (fn > tn) { @@ -470,7 +477,7 @@ h256s BlockChain::treeRoute(h256 const& _from, h256 const& _to, h256* o_common, ret.push_back(from); from = details(from).parent; fn--; - // cdebug << "from:" << fn << _from.abridged(); + cdebug << "from:" << fn << _from.abridged(); } h256 to = _to; while (fn < tn) @@ -479,12 +486,14 @@ h256s BlockChain::treeRoute(h256 const& _from, h256 const& _to, h256* o_common, back.push_back(to); to = details(to).parent; tn--; - // cdebug << "to:" << tn << _to.abridged(); + cdebug << "to:" << tn << _to.abridged(); } while (from != to) { - assert(from); - assert(to); + if (!from) + assert(from); + if (!to) + assert(to); from = details(from).parent; to = details(to).parent; if (_pre) diff --git a/libethereum/State.cpp b/libethereum/State.cpp index c52c90921..fa457dc41 100644 --- a/libethereum/State.cpp +++ b/libethereum/State.cpp @@ -725,7 +725,7 @@ void State::commitToMine(BlockChain const& _bc) uncommitToMine(); // cnote << "Committing to mine on block" << m_previousBlock.hash.abridged(); -#ifdef ETH_PARANOIA +#if ETH_PARANOIA && 0 commit(); cnote << "Pre-reward stateRoot:" << m_state.root(); #endif diff --git a/libevm/VM.h b/libevm/VM.h index b14a117df..1cf06b78b 100644 --- a/libevm/VM.h +++ b/libevm/VM.h @@ -56,7 +56,7 @@ public: virtual bytesConstRef go(ExtVMFace& _ext, OnOpFunc const& _onOp = {}, uint64_t _steps = (uint64_t)-1) override final; - void require(u256 _n, u256 _d) { if (m_stack.size() < _n) { if (m_onFail) m_onFail(); BOOST_THROW_EXCEPTION(StackUnderflow() << RequirementError((bigint)_n, (bigint)m_stack.size())); } if (m_stack.size() - _n + _d >= c_stackLimit) { if (m_onFail) m_onFail(); BOOST_THROW_EXCEPTION(OutOfStack() << RequirementError((bigint)(_d - _n), (bigint)m_stack.size())); } } + void require(u256 _n, u256 _d) { if (m_stack.size() < _n) { if (m_onFail) m_onFail(); BOOST_THROW_EXCEPTION(StackUnderflow() << RequirementError((bigint)_n, (bigint)m_stack.size())); } if (m_stack.size() - _n + _d > c_stackLimit) { if (m_onFail) m_onFail(); BOOST_THROW_EXCEPTION(OutOfStack() << RequirementError((bigint)(_d - _n), (bigint)m_stack.size())); } } void requireMem(unsigned _n) { if (m_temp.size() < _n) { m_temp.resize(_n); } } u256 curPC() const { return m_curPC; } diff --git a/libevmcore/Assembly.cpp b/libevmcore/Assembly.cpp index abe932282..904903761 100644 --- a/libevmcore/Assembly.cpp +++ b/libevmcore/Assembly.cpp @@ -34,9 +34,9 @@ void Assembly::append(Assembly const& _a) for (AssemblyItem i: _a.m_items) { if (i.type() == Tag || i.type() == PushTag) - i.m_data += m_usedTags; + i.setData(i.data() + m_usedTags); else if (i.type() == PushSub || i.type() == PushSubSize) - i.m_data += m_subs.size(); + i.setData(i.data() + m_usedTags); append(i); } m_deposit = newDeposit; @@ -106,34 +106,34 @@ ostream& Assembly::stream(ostream& _out, string const& _prefix, StringMap const& for (AssemblyItem const& i: m_items) { _out << _prefix; - switch (i.m_type) + switch (i.type()) { case Operation: _out << " " << instructionInfo(i.instruction()).name << "\t" << i.getJumpTypeAsString(); break; case Push: - _out << " PUSH " << i.m_data; + _out << " PUSH " << i.data(); break; case PushString: - _out << " PUSH \"" << m_strings.at((h256)i.m_data) << "\""; + _out << " PUSH \"" << m_strings.at((h256)i.data()) << "\""; break; case PushTag: - _out << " PUSH [tag" << i.m_data << "]"; + _out << " PUSH [tag" << i.data() << "]"; break; case PushSub: - _out << " PUSH [$" << h256(i.m_data).abridged() << "]"; + _out << " PUSH [$" << h256(i.data()).abridged() << "]"; break; case PushSubSize: - _out << " PUSH #[$" << h256(i.m_data).abridged() << "]"; + _out << " PUSH #[$" << h256(i.data()).abridged() << "]"; break; case PushProgramSize: _out << " PUSHSIZE"; break; case Tag: - _out << "tag" << i.m_data << ": " << endl << _prefix << " JUMPDEST"; + _out << "tag" << i.data() << ": " << endl << _prefix << " JUMPDEST"; break; case PushData: - _out << " PUSH [" << hex << (unsigned)i.m_data << "]"; + _out << " PUSH [" << hex << (unsigned)i.data() << "]"; break; default: BOOST_THROW_EXCEPTION(InvalidOpcode()); @@ -187,20 +187,9 @@ Assembly& Assembly::optimise(bool _enable) { if (!_enable) return *this; - std::vector>> rules = - { - { { Push, Instruction::POP }, [](AssemblyItemsConstRef) -> AssemblyItems { return {}; } }, - { { PushTag, Instruction::POP }, [](AssemblyItemsConstRef) -> AssemblyItems { return {}; } }, - { { PushString, Instruction::POP }, [](AssemblyItemsConstRef) -> AssemblyItems { return {}; } }, - { { PushSub, Instruction::POP }, [](AssemblyItemsConstRef) -> AssemblyItems { return {}; } }, - { { PushSubSize, Instruction::POP }, [](AssemblyItemsConstRef) -> AssemblyItems { return {}; } }, - { { PushProgramSize, Instruction::POP }, [](AssemblyItemsConstRef) -> AssemblyItems { return {}; } }, - { { Push, PushTag, Instruction::JUMPI }, [](AssemblyItemsConstRef m) -> AssemblyItems { if (m[0].data()) return { m[1], Instruction::JUMP }; else return {}; } }, - { { Instruction::ISZERO, Instruction::ISZERO }, [](AssemblyItemsConstRef) -> AssemblyItems { return {}; } }, - }; - + std::vector>> rules; // jump to next instruction - rules.push_back({ { PushTag, Instruction::JUMP, Tag }, [](AssemblyItemsConstRef m) -> AssemblyItems { if (m[0].m_data == m[2].m_data) return {m[2]}; else return m.toVector(); }}); + rules.push_back({ { PushTag, Instruction::JUMP, Tag }, [](AssemblyItemsConstRef m) -> AssemblyItems { if (m[0].data() == m[2].data()) return {m[2]}; else return m.toVector(); }}); unsigned total = 0; for (unsigned count = 1; count > 0; total += count) @@ -235,8 +224,6 @@ Assembly& Assembly::optimise(bool _enable) *orig = move(*moveIter); iter = m_items.erase(orig, iter); } - if (iter != m_items.end()) - ++iter; } for (unsigned i = 0; i < m_items.size(); ++i) @@ -344,16 +331,16 @@ bytes Assembly::assemble() const // m_data must not change from here on for (AssemblyItem const& i: m_items) - switch (i.m_type) + switch (i.type()) { case Operation: - ret.push_back((byte)i.m_data); + ret.push_back((byte)i.data()); break; case PushString: { ret.push_back((byte)Instruction::PUSH32); unsigned ii = 0; - for (auto j: m_strings.at((h256)i.m_data)) + for (auto j: m_strings.at((h256)i.data())) if (++ii > 32) break; else @@ -364,30 +351,30 @@ bytes Assembly::assemble() const } case Push: { - byte b = max(1, dev::bytesRequired(i.m_data)); + byte b = max(1, dev::bytesRequired(i.data())); ret.push_back((byte)Instruction::PUSH1 - 1 + b); ret.resize(ret.size() + b); bytesRef byr(&ret.back() + 1 - b, b); - toBigEndian(i.m_data, byr); + toBigEndian(i.data(), byr); break; } case PushTag: { ret.push_back(tagPush); - tagRef[ret.size()] = (unsigned)i.m_data; + tagRef[ret.size()] = (unsigned)i.data(); ret.resize(ret.size() + bytesPerTag); break; } case PushData: case PushSub: { ret.push_back(dataRefPush); - dataRef.insert(make_pair((h256)i.m_data, ret.size())); + dataRef.insert(make_pair((h256)i.data(), ret.size())); ret.resize(ret.size() + bytesPerDataRef); break; } case PushSubSize: { - auto s = m_data[i.m_data].size(); + auto s = m_data[i.data()].size(); byte b = max(1, dev::bytesRequired(s)); ret.push_back((byte)Instruction::PUSH1 - 1 + b); ret.resize(ret.size() + b); @@ -403,7 +390,7 @@ bytes Assembly::assemble() const break; } case Tag: - tagPos[(unsigned)i.m_data] = ret.size(); + tagPos[(unsigned)i.data()] = ret.size(); ret.push_back((byte)Instruction::JUMPDEST); break; default: diff --git a/libevmcore/Assembly.h b/libevmcore/Assembly.h index 315e6aaed..2744af900 100644 --- a/libevmcore/Assembly.h +++ b/libevmcore/Assembly.h @@ -65,7 +65,7 @@ public: template Assembly& operator<<(T const& _d) { append(_d); return *this; } AssemblyItems const& getItems() const { return m_items; } AssemblyItem const& back() const { return m_items.back(); } - std::string backString() const { return m_items.size() && m_items.back().m_type == PushString ? m_strings.at((h256)m_items.back().m_data) : std::string(); } + std::string backString() const { return m_items.size() && m_items.back().type() == PushString ? m_strings.at((h256)m_items.back().data()) : std::string(); } void onePath() { if (asserts(!m_totalDeposit && !m_baseDeposit)) BOOST_THROW_EXCEPTION(InvalidDeposit()); m_baseDeposit = m_deposit; m_totalDeposit = INT_MAX; } void otherPath() { donePath(); m_totalDeposit = m_deposit; m_deposit = m_baseDeposit; } diff --git a/libevmcore/AssemblyItem.h b/libevmcore/AssemblyItem.h index e7beced39..3e222a6eb 100644 --- a/libevmcore/AssemblyItem.h +++ b/libevmcore/AssemblyItem.h @@ -40,8 +40,6 @@ class Assembly; class AssemblyItem { - friend class Assembly; - public: enum class JumpType { Ordinary, IntoFunction, OutOfFunction }; @@ -54,6 +52,9 @@ public: AssemblyItemType type() const { return m_type; } u256 const& data() const { return m_data; } + void setType(AssemblyItemType const _type) { m_type = _type; } + void setData(u256 const& _data) { m_data = _data; } + /// @returns the instruction of this item (only valid if type() == Operation) Instruction instruction() const { return Instruction(byte(m_data)); } diff --git a/libevmcore/CommonSubexpressionEliminator.cpp b/libevmcore/CommonSubexpressionEliminator.cpp index 7fed03b4e..5c6ba95af 100644 --- a/libevmcore/CommonSubexpressionEliminator.cpp +++ b/libevmcore/CommonSubexpressionEliminator.cpp @@ -24,7 +24,7 @@ #include #include #include -#include +#include using namespace std; using namespace dev; @@ -32,6 +32,8 @@ using namespace dev::eth; vector CommonSubexpressionEliminator::getOptimizedItems() { + optimizeBreakingItem(); + map initialStackContents; map targetStackContents; int minHeight = m_stackHeight + 1; @@ -45,19 +47,27 @@ vector CommonSubexpressionEliminator::getOptimizedItems() // Debug info: //stream(cout, initialStackContents, targetStackContents); - return CSECodeGenerator(m_expressionClasses).generateCode(initialStackContents, targetStackContents); + AssemblyItems items = CSECodeGenerator(m_expressionClasses, m_storeOperations).generateCode( + initialStackContents, + targetStackContents + ); + if (m_breakingItem) + items.push_back(*m_breakingItem); + return items; } ostream& CommonSubexpressionEliminator::stream( ostream& _out, - map _currentStack, + map _initialStack, map _targetStack ) const { auto streamExpressionClass = [this](ostream& _out, ExpressionClasses::Id _id) { auto const& expr = m_expressionClasses.representative(_id); - _out << " " << _id << ": " << *expr.item; + _out << " " << dec << _id << ": " << *expr.item; + if (expr.sequenceNumber) + _out << "@" << dec << expr.sequenceNumber; _out << "("; for (ExpressionClasses::Id arg: expr.arguments) _out << dec << arg << ","; @@ -66,18 +76,12 @@ ostream& CommonSubexpressionEliminator::stream( _out << "Optimizer analysis:" << endl; _out << "Final stack height: " << dec << m_stackHeight << endl; - _out << "Stack elements: " << endl; - for (auto const& it: m_stackElements) - { - _out << " " << dec << it.first << " = "; - streamExpressionClass(_out, it.second); - } _out << "Equivalence classes: " << endl; for (ExpressionClasses::Id eqClass = 0; eqClass < m_expressionClasses.size(); ++eqClass) streamExpressionClass(_out, eqClass); - _out << "Current stack: " << endl; - for (auto const& it: _currentStack) + _out << "Initial stack: " << endl; + for (auto const& it: _initialStack) { _out << " " << dec << it.first << ": "; streamExpressionClass(_out, it.second); @@ -92,13 +96,12 @@ ostream& CommonSubexpressionEliminator::stream( return _out; } -void CommonSubexpressionEliminator::feedItem(AssemblyItem const& _item) +void CommonSubexpressionEliminator::feedItem(AssemblyItem const& _item, bool _copyItem) { if (_item.type() != Operation) { - if (_item.deposit() != 1) - BOOST_THROW_EXCEPTION(InvalidDeposit()); - setStackElement(++m_stackHeight, m_expressionClasses.find(_item, {})); + assertThrow(_item.deposit() == 1, InvalidDeposit, ""); + setStackElement(++m_stackHeight, m_expressionClasses.find(_item, {}, _copyItem)); } else { @@ -119,12 +122,47 @@ void CommonSubexpressionEliminator::feedItem(AssemblyItem const& _item) vector arguments(info.args); for (int i = 0; i < info.args; ++i) arguments[i] = stackElement(m_stackHeight - i); - setStackElement(m_stackHeight + _item.deposit(), m_expressionClasses.find(_item, arguments)); + if (_item.instruction() == Instruction::SSTORE) + storeInStorage(arguments[0], arguments[1]); + else if (_item.instruction() == Instruction::SLOAD) + setStackElement(m_stackHeight + _item.deposit(), loadFromStorage(arguments[0])); + else if (_item.instruction() == Instruction::MSTORE) + storeInMemory(arguments[0], arguments[1]); + else if (_item.instruction() == Instruction::MLOAD) + setStackElement(m_stackHeight + _item.deposit(), loadFromMemory(arguments[0])); + else + setStackElement(m_stackHeight + _item.deposit(), m_expressionClasses.find(_item, arguments, _copyItem)); } m_stackHeight += _item.deposit(); } } +void CommonSubexpressionEliminator::optimizeBreakingItem() +{ + if (!m_breakingItem || *m_breakingItem != AssemblyItem(Instruction::JUMPI)) + return; + + using Id = ExpressionClasses::Id; + static AssemblyItem s_jump = Instruction::JUMP; + + Id condition = stackElement(m_stackHeight - 1); + Id zero = m_expressionClasses.find(u256(0)); + if (m_expressionClasses.knownToBeDifferent(condition, zero)) + { + feedItem(Instruction::SWAP1, true); + feedItem(Instruction::POP, true); + m_breakingItem = &s_jump; + return; + } + Id negatedCondition = m_expressionClasses.find(Instruction::ISZERO, {condition}); + if (m_expressionClasses.knownToBeDifferent(negatedCondition, zero)) + { + feedItem(Instruction::POP, true); + feedItem(Instruction::POP, true); + m_breakingItem = nullptr; + } +} + void CommonSubexpressionEliminator::setStackElement(int _stackHeight, ExpressionClasses::Id _class) { m_stackElements[_stackHeight] = _class; @@ -132,8 +170,7 @@ void CommonSubexpressionEliminator::setStackElement(int _stackHeight, Expression void CommonSubexpressionEliminator::swapStackElements(int _stackHeightA, int _stackHeightB) { - if (_stackHeightA == _stackHeightB) - BOOST_THROW_EXCEPTION(OptimizerException() << errinfo_comment("Swap on same stack elements.")); + assertThrow(_stackHeightA != _stackHeightB, OptimizerException, "Swap on same stack elements."); // ensure they are created stackElement(_stackHeightA); stackElement(_stackHeightB); @@ -157,65 +194,68 @@ ExpressionClasses::Id CommonSubexpressionEliminator::initialStackElement(int _st return m_expressionClasses.find(AssemblyItem(dupInstruction(1 - _stackHeight))); } -bool SemanticInformation::breaksBasicBlock(AssemblyItem const& _item) +void CommonSubexpressionEliminator::storeInStorage(ExpressionClasses::Id _slot, ExpressionClasses::Id _value) { - switch (_item.type()) - { - default: - case UndefinedItem: - case Tag: - return true; - case Push: - case PushString: - case PushTag: - case PushSub: - case PushSubSize: - case PushProgramSize: - case PushData: - return false; - case Operation: - { - if (isSwapInstruction(_item) || isDupInstruction(_item)) - return false; - if (_item.instruction() == Instruction::GAS || _item.instruction() == Instruction::PC) - return true; // GAS and PC assume a specific order of opcodes - InstructionInfo info = instructionInfo(_item.instruction()); - // the second requirement will be lifted once it is implemented - return info.sideEffects || info.args > 2; - } - } + if (m_storageContent.count(_slot) && m_storageContent[_slot] == _value) + // do not execute the storage if we know that the value is already there + return; + m_sequenceNumber++; + decltype(m_storageContent) storageContents; + // copy over values at points where we know that they are different from _slot + for (auto const& storageItem: m_storageContent) + if (m_expressionClasses.knownToBeDifferent(storageItem.first, _slot)) + storageContents.insert(storageItem); + m_storageContent = move(storageContents); + ExpressionClasses::Id id = m_expressionClasses.find(Instruction::SSTORE, {_slot, _value}, true, m_sequenceNumber); + m_storeOperations.push_back(StoreOperation(StoreOperation::Storage, _slot, m_sequenceNumber, id)); + m_storageContent[_slot] = _value; + // increment a second time so that we get unique sequence numbers for writes + m_sequenceNumber++; } -bool SemanticInformation::isCommutativeOperation(AssemblyItem const& _item) +ExpressionClasses::Id CommonSubexpressionEliminator::loadFromStorage(ExpressionClasses::Id _slot) { - if (_item.type() != Operation) - return false; - switch (_item.instruction()) - { - case Instruction::ADD: - case Instruction::MUL: - case Instruction::EQ: - case Instruction::AND: - case Instruction::OR: - case Instruction::XOR: - return true; - default: - return false; - } + if (m_storageContent.count(_slot)) + return m_storageContent.at(_slot); + else + return m_storageContent[_slot] = m_expressionClasses.find(Instruction::SLOAD, {_slot}, true, m_sequenceNumber); } -bool SemanticInformation::isDupInstruction(AssemblyItem const& _item) +void CommonSubexpressionEliminator::storeInMemory(ExpressionClasses::Id _slot, ExpressionClasses::Id _value) { - if (_item.type() != Operation) - return false; - return Instruction::DUP1 <= _item.instruction() && _item.instruction() <= Instruction::DUP16; + if (m_memoryContent.count(_slot) && m_memoryContent[_slot] == _value) + // do not execute the store if we know that the value is already there + return; + m_sequenceNumber++; + decltype(m_memoryContent) memoryContents; + // copy over values at points where we know that they are different from _slot by at least 32 + for (auto const& memoryItem: m_memoryContent) + if (m_expressionClasses.knownToBeDifferentBy32(memoryItem.first, _slot)) + memoryContents.insert(memoryItem); + m_memoryContent = move(memoryContents); + ExpressionClasses::Id id = m_expressionClasses.find(Instruction::MSTORE, {_slot, _value}, true, m_sequenceNumber); + m_storeOperations.push_back(StoreOperation(StoreOperation::Memory, _slot, m_sequenceNumber, id)); + m_memoryContent[_slot] = _value; + // increment a second time so that we get unique sequence numbers for writes + m_sequenceNumber++; } -bool SemanticInformation::isSwapInstruction(AssemblyItem const& _item) +ExpressionClasses::Id CommonSubexpressionEliminator::loadFromMemory(ExpressionClasses::Id _slot) { - if (_item.type() != Operation) - return false; - return Instruction::SWAP1 <= _item.instruction() && _item.instruction() <= Instruction::SWAP16; + if (m_memoryContent.count(_slot)) + return m_memoryContent.at(_slot); + else + return m_memoryContent[_slot] = m_expressionClasses.find(Instruction::MLOAD, {_slot}, true, m_sequenceNumber); +} + +CSECodeGenerator::CSECodeGenerator( + ExpressionClasses& _expressionClasses, + vector const& _storeOperations +): + m_expressionClasses(_expressionClasses) +{ + for (auto const& store: _storeOperations) + m_storeOperations[make_pair(store.target, store.slot)].push_back(store); } AssemblyItems CSECodeGenerator::generateCode( @@ -230,26 +270,40 @@ AssemblyItems CSECodeGenerator::generateCode( // @todo: provide information about the positions of copies of class elements - // generate the dependency graph + // generate the dependency graph starting from final storage and memory writes and target stack contents + for (auto const& p: m_storeOperations) + addDependencies(p.second.back().expression); for (auto const& targetItem: _targetStackContents) { m_finalClasses.insert(targetItem.second); addDependencies(targetItem.second); } - // generate the actual elements + // store all needed sequenced expressions + set> sequencedExpressions; + for (auto const& p: m_neededBy) + for (auto id: {p.first, p.second}) + if (unsigned seqNr = m_expressionClasses.representative(id).sequenceNumber) + sequencedExpressions.insert(make_pair(seqNr, id)); + + // Perform all operations on storage and memory in order, if they are needed. + for (auto const& seqAndId: sequencedExpressions) + if (!m_classPositions.count(seqAndId.second)) + generateClassElement(seqAndId.second, true); + + // generate the target stack elements for (auto const& targetItem: _targetStackContents) { - removeStackTopIfPossible(); int position = generateClassElement(targetItem.second); + assertThrow(position != c_invalidPosition, OptimizerException, ""); if (position == targetItem.first) continue; if (position < targetItem.first) // it is already at its target, we need another copy appendDup(position); else - appendSwapOrRemove(position); - appendSwapOrRemove(targetItem.first); + appendOrRemoveSwap(position); + appendOrRemoveSwap(targetItem.first); } // remove surplus elements @@ -270,23 +324,59 @@ AssemblyItems CSECodeGenerator::generateCode( // neither initial no target stack, no change in height finalHeight = 0; assertThrow(finalHeight == m_stackHeight, OptimizerException, "Incorrect final stack height."); - return m_generatedItems; } void CSECodeGenerator::addDependencies(ExpressionClasses::Id _c) { if (m_neededBy.count(_c)) - return; - for (ExpressionClasses::Id argument: m_expressionClasses.representative(_c).arguments) + return; // we already computed the dependencies for _c + ExpressionClasses::Expression expr = m_expressionClasses.representative(_c); + for (ExpressionClasses::Id argument: expr.arguments) { addDependencies(argument); m_neededBy.insert(make_pair(argument, _c)); } + if (expr.item->type() == Operation && ( + expr.item->instruction() == Instruction::SLOAD || + expr.item->instruction() == Instruction::MLOAD + )) + { + // this loads an unknown value from storage or memory and thus, in addition to its + // arguments, depends on all store operations to addresses where we do not know that + // they are different that occur before this load + StoreOperation::Target target = expr.item->instruction() == Instruction::SLOAD ? + StoreOperation::Storage : StoreOperation::Memory; + ExpressionClasses::Id slotToLoadFrom = expr.arguments.at(0); + for (auto const& p: m_storeOperations) + { + if (p.first.first != target) + continue; + ExpressionClasses::Id slot = p.first.second; + StoreOperations const& storeOps = p.second; + if (storeOps.front().sequenceNumber > expr.sequenceNumber) + continue; + if ( + (target == StoreOperation::Memory && m_expressionClasses.knownToBeDifferentBy32(slot, slotToLoadFrom)) || + (target == StoreOperation::Storage && m_expressionClasses.knownToBeDifferent(slot, slotToLoadFrom)) + ) + continue; + // note that store and load never have the same sequence number + ExpressionClasses::Id latestStore = storeOps.front().expression; + for (auto it = ++storeOps.begin(); it != storeOps.end(); ++it) + if (it->sequenceNumber < expr.sequenceNumber) + latestStore = it->expression; + addDependencies(latestStore); + m_neededBy.insert(make_pair(latestStore, _c)); + } + } } -int CSECodeGenerator::generateClassElement(ExpressionClasses::Id _c) +int CSECodeGenerator::generateClassElement(ExpressionClasses::Id _c, bool _allowSequenced) { + // do some cleanup + removeStackTopIfPossible(); + if (m_classPositions.count(_c)) { assertThrow( @@ -296,7 +386,13 @@ int CSECodeGenerator::generateClassElement(ExpressionClasses::Id _c) ); return m_classPositions[_c]; } - ExpressionClasses::Ids const& arguments = m_expressionClasses.representative(_c).arguments; + ExpressionClasses::Expression const& expr = m_expressionClasses.representative(_c); + assertThrow( + _allowSequenced || expr.sequenceNumber == 0, + OptimizerException, + "Sequence constrained operation requested out of sequence." + ); + ExpressionClasses::Ids const& arguments = expr.arguments; for (ExpressionClasses::Id arg: boost::adaptors::reverse(arguments)) generateClassElement(arg); @@ -307,42 +403,42 @@ int CSECodeGenerator::generateClassElement(ExpressionClasses::Id _c) if (arguments.size() == 1) { if (canBeRemoved(arguments[0], _c)) - appendSwapOrRemove(generateClassElement(arguments[0])); + appendOrRemoveSwap(classElementPosition(arguments[0])); else - appendDup(generateClassElement(arguments[0])); + appendDup(classElementPosition(arguments[0])); } else if (arguments.size() == 2) { if (canBeRemoved(arguments[1], _c)) { - appendSwapOrRemove(generateClassElement(arguments[1])); + appendOrRemoveSwap(classElementPosition(arguments[1])); if (arguments[0] == arguments[1]) appendDup(m_stackHeight); else if (canBeRemoved(arguments[0], _c)) { - appendSwapOrRemove(m_stackHeight - 1); - appendSwapOrRemove(generateClassElement(arguments[0])); + appendOrRemoveSwap(m_stackHeight - 1); + appendOrRemoveSwap(classElementPosition(arguments[0])); } else - appendDup(generateClassElement(arguments[0])); + appendDup(classElementPosition(arguments[0])); } else { if (arguments[0] == arguments[1]) { - appendDup(generateClassElement(arguments[0])); + appendDup(classElementPosition(arguments[0])); appendDup(m_stackHeight); } else if (canBeRemoved(arguments[0], _c)) { - appendSwapOrRemove(generateClassElement(arguments[0])); - appendDup(generateClassElement(arguments[1])); - appendSwapOrRemove(m_stackHeight - 1); + appendOrRemoveSwap(classElementPosition(arguments[0])); + appendDup(classElementPosition(arguments[1])); + appendOrRemoveSwap(m_stackHeight - 1); } else { - appendDup(generateClassElement(arguments[1])); - appendDup(generateClassElement(arguments[0])); + appendDup(classElementPosition(arguments[1])); + appendDup(classElementPosition(arguments[0])); } } } @@ -355,20 +451,41 @@ int CSECodeGenerator::generateClassElement(ExpressionClasses::Id _c) for (size_t i = 0; i < arguments.size(); ++i) assertThrow(m_stack[m_stackHeight - i] == arguments[i], OptimizerException, "Expected arguments not present." ); - AssemblyItem const& item = *m_expressionClasses.representative(_c).item; - while (SemanticInformation::isCommutativeOperation(item) && + while (SemanticInformation::isCommutativeOperation(*expr.item) && !m_generatedItems.empty() && m_generatedItems.back() == AssemblyItem(Instruction::SWAP1)) // this will not append a swap but remove the one that is already there - appendSwapOrRemove(m_stackHeight - 1); + appendOrRemoveSwap(m_stackHeight - 1); for (auto arg: arguments) if (canBeRemoved(arg, _c)) m_classPositions[arg] = c_invalidPosition; for (size_t i = 0; i < arguments.size(); ++i) m_stack.erase(m_stackHeight - i); - appendItem(*m_expressionClasses.representative(_c).item); - m_stack[m_stackHeight] = _c; - return m_classPositions[_c] = m_stackHeight; + appendItem(*expr.item); + if (expr.item->type() != Operation || instructionInfo(expr.item->instruction()).ret == 1) + { + m_stack[m_stackHeight] = _c; + return m_classPositions[_c] = m_stackHeight; + } + else + { + assertThrow( + instructionInfo(expr.item->instruction()).ret == 0, + OptimizerException, + "Invalid number of return values." + ); + return m_classPositions[_c] = c_invalidPosition; + } +} + +int CSECodeGenerator::classElementPosition(ExpressionClasses::Id _id) const +{ + assertThrow( + m_classPositions.count(_id) && m_classPositions.at(_id) != c_invalidPosition, + OptimizerException, + "Element requested but is not present." + ); + return m_classPositions.at(_id); } bool CSECodeGenerator::canBeRemoved(ExpressionClasses::Id _element, ExpressionClasses::Id _result) @@ -401,22 +518,23 @@ bool CSECodeGenerator::removeStackTopIfPossible() void CSECodeGenerator::appendDup(int _fromPosition) { - int nr = 1 + m_stackHeight - _fromPosition; - assertThrow(nr <= 16, StackTooDeepException, "Stack too deep."); - assertThrow(1 <= nr, OptimizerException, "Invalid stack access."); - m_generatedItems.push_back(AssemblyItem(dupInstruction(nr))); - m_stackHeight++; + assertThrow(_fromPosition != c_invalidPosition, OptimizerException, ""); + int instructionNum = 1 + m_stackHeight - _fromPosition; + assertThrow(instructionNum <= 16, StackTooDeepException, "Stack too deep."); + assertThrow(1 <= instructionNum, OptimizerException, "Invalid stack access."); + appendItem(AssemblyItem(dupInstruction(instructionNum))); m_stack[m_stackHeight] = m_stack[_fromPosition]; } -void CSECodeGenerator::appendSwapOrRemove(int _fromPosition) +void CSECodeGenerator::appendOrRemoveSwap(int _fromPosition) { + assertThrow(_fromPosition != c_invalidPosition, OptimizerException, ""); if (_fromPosition == m_stackHeight) return; - int nr = m_stackHeight - _fromPosition; - assertThrow(nr <= 16, StackTooDeepException, "Stack too deep."); - assertThrow(1 <= nr, OptimizerException, "Invalid stack access."); - m_generatedItems.push_back(AssemblyItem(swapInstruction(nr))); + int instructionNum = m_stackHeight - _fromPosition; + assertThrow(instructionNum <= 16, StackTooDeepException, "Stack too deep."); + assertThrow(1 <= instructionNum, OptimizerException, "Invalid stack access."); + appendItem(AssemblyItem(swapInstruction(instructionNum))); // The value of a class can be present in multiple locations on the stack. We only update the // "canonical" one that is tracked by m_classPositions if (m_classPositions[m_stack[m_stackHeight]] == m_stackHeight) diff --git a/libevmcore/CommonSubexpressionEliminator.h b/libevmcore/CommonSubexpressionEliminator.h index 331a7642d..0dbb47b29 100644 --- a/libevmcore/CommonSubexpressionEliminator.h +++ b/libevmcore/CommonSubexpressionEliminator.h @@ -25,10 +25,13 @@ #include #include +#include +#include #include #include #include #include +#include namespace dev { @@ -44,9 +47,9 @@ using AssemblyItems = std::vector; * known to be equal only once. * * The general workings are that for each assembly item that is fed into the eliminator, an - * equivalence class is derived from the operation and the equivalence class of its arguments and - * it is assigned to the next sequence number of a stack item. DUPi, SWAPi and some arithmetic - * instructions are used to infer equivalences while these classes are determined. + * equivalence class is derived from the operation and the equivalence class of its arguments. + * DUPi, SWAPi and some arithmetic instructions are used to infer equivalences while these + * classes are determined. * * When the list of optimized items is requested, they are generated in a bottom-up fashion, * adding code for equivalence classes that were not yet computed. @@ -54,6 +57,21 @@ using AssemblyItems = std::vector; class CommonSubexpressionEliminator { public: + struct StoreOperation + { + enum Target { Memory, Storage }; + StoreOperation( + Target _target, + ExpressionClasses::Id _slot, + unsigned _sequenceNumber, + ExpressionClasses::Id _expression + ): target(_target), slot(_slot), sequenceNumber(_sequenceNumber), expression(_expression) {} + Target target; + ExpressionClasses::Id slot; + unsigned sequenceNumber; + ExpressionClasses::Id expression; + }; + /// Feeds AssemblyItems into the eliminator and @returns the iterator pointing at the first /// item that must be fed into a new instance of the eliminator. template @@ -65,13 +83,16 @@ public: /// Streams debugging information to @a _out. std::ostream& stream( std::ostream& _out, - std::map _currentStack = std::map(), + std::map _initialStack = std::map(), std::map _targetStack = std::map() ) const; private: /// Feeds the item into the system for analysis. - void feedItem(AssemblyItem const& _item); + void feedItem(AssemblyItem const& _item, bool _copyItem = false); + + /// Tries to optimize the item that breaks the basic block at the end. + void optimizeBreakingItem(); /// Simplifies the given item using /// Assigns a new equivalence class to the next sequence number of the given stack element. @@ -85,26 +106,38 @@ private: /// (must not be positive). ExpressionClasses::Id initialStackElement(int _stackHeight); + /// Increments the sequence number, deletes all storage information that might be overwritten + /// and stores the new value at the given slot. + void storeInStorage(ExpressionClasses::Id _slot, ExpressionClasses::Id _value); + /// Retrieves the current value at the given slot in storage or creates a new special sload class. + ExpressionClasses::Id loadFromStorage(ExpressionClasses::Id _slot); + /// Increments the sequence number, deletes all memory information that might be overwritten + /// and stores the new value at the given slot. + void storeInMemory(ExpressionClasses::Id _slot, ExpressionClasses::Id _value); + /// Retrieves the current value at the given slot in memory or creates a new special mload class. + ExpressionClasses::Id loadFromMemory(ExpressionClasses::Id _slot); + + /// Current stack height, can be negative. int m_stackHeight = 0; /// Current stack layout, mapping stack height -> equivalence class std::map m_stackElements; + /// Current sequence number, this is incremented with each modification to storage or memory. + unsigned m_sequenceNumber = 1; + /// Knowledge about storage content. + std::map m_storageContent; + /// Knowledge about memory content. Keys are memory addresses, note that the values overlap + /// and are not contained here if they are not completely known. + std::map m_memoryContent; + /// Keeps information about which storage or memory slots were written to at which sequence + /// number with what instruction. + std::vector m_storeOperations; /// Structure containing the classes of equivalent expressions. ExpressionClasses m_expressionClasses; -}; -/** - * Helper functions to provide context-independent information about assembly items. - */ -struct SemanticInformation -{ - /// @returns true if the given items starts a new basic block - static bool breaksBasicBlock(AssemblyItem const& _item); - /// @returns true if the item is a two-argument operation whose value does not depend on the - /// order of its arguments. - static bool isCommutativeOperation(AssemblyItem const& _item); - static bool isDupInstruction(AssemblyItem const& _item); - static bool isSwapInstruction(AssemblyItem const& _item); + /// The item that breaks the basic block, can be nullptr. + /// It is usually appended to the block but can be optimized in some cases. + AssemblyItem const* m_breakingItem = nullptr; }; /** @@ -114,14 +147,16 @@ struct SemanticInformation class CSECodeGenerator { public: - CSECodeGenerator(ExpressionClasses const& _expressionClasses): - m_expressionClasses(_expressionClasses) - {} + using StoreOperation = CommonSubexpressionEliminator::StoreOperation; + using StoreOperations = std::vector; + + /// Initializes the code generator with the given classes and store operations. + /// The store operations have to be sorted by sequence number in ascending order. + CSECodeGenerator(ExpressionClasses& _expressionClasses, StoreOperations const& _storeOperations); /// @returns the assembly items generated from the given requirements /// @param _initialStack current contents of the stack (up to stack height of zero) /// @param _targetStackContents final contents of the stack, by stack height relative to initial - /// @param _equivalenceClasses equivalence classes as expressions of how to compute them /// @note should only be called once on each object. AssemblyItems generateCode( std::map const& _initialStack, @@ -133,8 +168,13 @@ private: void addDependencies(ExpressionClasses::Id _c); /// Produce code that generates the given element if it is not yet present. - /// @returns the stack position of the element. - int generateClassElement(ExpressionClasses::Id _c); + /// @returns the stack position of the element or c_invalidPosition if it does not actually + /// generate a value on the stack. + /// @param _allowSequenced indicates that sequence-constrained operations are allowed + int generateClassElement(ExpressionClasses::Id _c, bool _allowSequenced = false); + /// @returns the position of the representative of the given id on the stack. + /// @note throws an exception if it is not on the stack. + int classElementPosition(ExpressionClasses::Id _id) const; /// @returns true if @a _element can be removed - in general or, if given, while computing @a _result. bool canBeRemoved(ExpressionClasses::Id _element, ExpressionClasses::Id _result = ExpressionClasses::Id(-1)); @@ -146,7 +186,7 @@ private: void appendDup(int _fromPosition); /// Appends a swap instruction to m_generatedItems to retrieve the element at the given stack position. /// @note this might also remove the last item if it exactly the same swap instruction. - void appendSwapOrRemove(int _fromPosition); + void appendOrRemoveSwap(int _fromPosition); /// Appends the given assembly item. void appendItem(AssemblyItem const& _item); @@ -163,7 +203,10 @@ private: std::map m_classPositions; /// The actual eqivalence class items and how to compute them. - ExpressionClasses const& m_expressionClasses; + ExpressionClasses& m_expressionClasses; + /// Keeps information about which storage or memory slots were written to by which operations. + /// The operations are sorted ascendingly by sequence number. + std::map, StoreOperations> m_storeOperations; /// The set of equivalence classes that should be present on the stack at the end. std::set m_finalClasses; }; @@ -174,8 +217,10 @@ _AssemblyItemIterator CommonSubexpressionEliminator::feedItems( _AssemblyItemIterator _end ) { - for (; _iterator != _end && !SemanticInformation::breaksBasicBlock(*_iterator); ++_iterator) + for (; _iterator != _end && !SemanticInformation::breaksCSEAnalysisBlock(*_iterator); ++_iterator) feedItem(*_iterator); + if (_iterator != _end) + m_breakingItem = &(*_iterator++); return _iterator; } diff --git a/libevmcore/ExpressionClasses.cpp b/libevmcore/ExpressionClasses.cpp index b43b54113..1c7a79e6b 100644 --- a/libevmcore/ExpressionClasses.cpp +++ b/libevmcore/ExpressionClasses.cpp @@ -39,41 +39,71 @@ bool ExpressionClasses::Expression::operator<(ExpressionClasses::Expression cons { auto type = item->type(); auto otherType = _other.item->type(); - return std::tie(type, item->data(), arguments) < - std::tie(otherType, _other.item->data(), _other.arguments); + return std::tie(type, item->data(), arguments, sequenceNumber) < + std::tie(otherType, _other.item->data(), _other.arguments, _other.sequenceNumber); } -ExpressionClasses::Id ExpressionClasses::find(AssemblyItem const& _item, Ids const& _arguments) +ExpressionClasses::Id ExpressionClasses::find( + AssemblyItem const& _item, + Ids const& _arguments, + bool _copyItem, + unsigned _sequenceNumber +) { Expression exp; exp.id = Id(-1); exp.item = &_item; exp.arguments = _arguments; + exp.sequenceNumber = _sequenceNumber; if (SemanticInformation::isCommutativeOperation(_item)) sort(exp.arguments.begin(), exp.arguments.end()); - //@todo store all class members (not only the representatives) in an efficient data structure to search here - for (Expression const& e: m_representatives) - if (!(e < exp || exp < e)) - return e.id; + auto it = m_expressions.find(exp); + if (it != m_expressions.end()) + return it->id; - if (SemanticInformation::isDupInstruction(_item)) + if (_copyItem) { - // Special item that refers to values pre-existing on the stack m_spareAssemblyItem.push_back(make_shared(_item)); exp.item = m_spareAssemblyItem.back().get(); } ExpressionClasses::Id id = tryToSimplify(exp); if (id < m_representatives.size()) - return id; - - exp.id = m_representatives.size(); - m_representatives.push_back(exp); + exp.id = id; + else + { + exp.id = m_representatives.size(); + m_representatives.push_back(exp); + } + m_expressions.insert(exp); return exp.id; } +bool ExpressionClasses::knownToBeDifferent(ExpressionClasses::Id _a, ExpressionClasses::Id _b) +{ + // Try to simplify "_a - _b" and return true iff the value is a non-zero constant. + map matchGroups; + Pattern constant(Push); + constant.setMatchGroup(1, matchGroups); + Id difference = find(Instruction::SUB, {_a, _b}); + return constant.matches(representative(difference), *this) && constant.d() != u256(0); +} + +bool ExpressionClasses::knownToBeDifferentBy32(ExpressionClasses::Id _a, ExpressionClasses::Id _b) +{ + // Try to simplify "_a - _b" and return true iff the value is at least 32 away from zero. + map matchGroups; + Pattern constant(Push); + constant.setMatchGroup(1, matchGroups); + Id difference = find(Instruction::SUB, {_a, _b}); + if (!constant.matches(representative(difference), *this)) + return false; + // forbidden interval is ["-31", 31] + return constant.d() + 31 > u256(62); +} + string ExpressionClasses::fullDAGToString(ExpressionClasses::Id _id) const { Expression const& expr = representative(_id); @@ -189,27 +219,46 @@ Rules::Rules() // Moving constants to the outside, order matters here! // we need actions that return expressions (or patterns?) here, and we need also reversed rules // (X+A)+B -> X+(A+B) - m_rules.push_back({ + m_rules += vector>>{{ {op, {{op, {X, A}}, B}}, [=]() -> Pattern { return {op, {X, fun(A.d(), B.d())}}; } - }); + }, { // X+(Y+A) -> (X+Y)+A - m_rules.push_back({ {op, {{op, {X, A}}, Y}}, [=]() -> Pattern { return {op, {{op, {X, Y}}, A}}; } - }); + }, { // For now, we still need explicit commutativity for the inner pattern - m_rules.push_back({ {op, {{op, {A, X}}, B}}, [=]() -> Pattern { return {op, {X, fun(A.d(), B.d())}}; } - }); - m_rules.push_back({ + }, { {op, {{op, {A, X}}, Y}}, [=]() -> Pattern { return {op, {{op, {X, Y}}, A}}; } - }); + }}; + } + // move constants across subtractions + m_rules += vector>>{ + { + // X - A -> X + (-A) + {Instruction::SUB, {X, A}}, + [=]() -> Pattern { return {Instruction::ADD, {X, 0 - A.d()}}; } + }, { + // (X + A) - Y -> (X - Y) + A + {Instruction::SUB, {{Instruction::ADD, {X, A}}, Y}}, + [=]() -> Pattern { return {Instruction::ADD, {{Instruction::SUB, {X, Y}}, A}}; } + }, { + // (A + X) - Y -> (X - Y) + A + {Instruction::SUB, {{Instruction::ADD, {A, X}}, Y}}, + [=]() -> Pattern { return {Instruction::ADD, {{Instruction::SUB, {X, Y}}, A}}; } + }, { + // X - (Y + A) -> (X - Y) + (-A) + {Instruction::SUB, {X, {Instruction::ADD, {Y, A}}}}, + [=]() -> Pattern { return {Instruction::ADD, {{Instruction::SUB, {X, Y}}, 0 - A.d()}}; } + }, { + // X - (A + Y) -> (X - Y) + (-A) + {Instruction::SUB, {X, {Instruction::ADD, {A, Y}}}}, + [=]() -> Pattern { return {Instruction::ADD, {{Instruction::SUB, {X, Y}}, 0 - A.d()}}; } + } }; - - //@todo: (x+8)-3 and other things } ExpressionClasses::Id ExpressionClasses::tryToSimplify(Expression const& _expr, bool _secondRun) @@ -231,7 +280,7 @@ ExpressionClasses::Id ExpressionClasses::tryToSimplify(Expression const& _expr, //cout << ")" << endl; //cout << "with rule " << rule.first.toString() << endl; //ExpressionTemplate t(rule.second()); - //cout << "to" << rule.second().toString() << endl; + //cout << "to " << rule.second().toString() << endl; return rebuildExpression(ExpressionTemplate(rule.second())); } } @@ -254,8 +303,7 @@ ExpressionClasses::Id ExpressionClasses::rebuildExpression(ExpressionTemplate co Ids arguments; for (ExpressionTemplate const& t: _template.arguments) arguments.push_back(rebuildExpression(t)); - m_spareAssemblyItem.push_back(make_shared(_template.item)); - return find(*m_spareAssemblyItem.back(), arguments); + return find(_template.item, arguments); } diff --git a/libevmcore/ExpressionClasses.h b/libevmcore/ExpressionClasses.h index eda568e23..5179845f6 100644 --- a/libevmcore/ExpressionClasses.h +++ b/libevmcore/ExpressionClasses.h @@ -52,17 +52,33 @@ public: Id id; AssemblyItem const* item; Ids arguments; + unsigned sequenceNumber; ///< Storage modification sequence, only used for SLOAD/SSTORE instructions. + /// Behaves as if this was a tuple of (item->type(), item->data(), arguments, sequenceNumber). bool operator<(Expression const& _other) const; }; /// Retrieves the id of the expression equivalence class resulting from the given item applied to the /// given classes, might also create a new one. - Id find(AssemblyItem const& _item, Ids const& _arguments = {}); + /// @param _copyItem if true, copies the assembly item to an internal storage instead of just + /// keeping a pointer. + /// The @a _sequenceNumber indicates the current storage or memory access sequence. + Id find( + AssemblyItem const& _item, + Ids const& _arguments = {}, + bool _copyItem = true, + unsigned _sequenceNumber = 0 + ); /// @returns the canonical representative of an expression class. Expression const& representative(Id _id) const { return m_representatives.at(_id); } /// @returns the number of classes. Id size() const { return m_representatives.size(); } + /// @returns true if the values of the given classes are known to be different (on every input). + /// @note that this function might still return false for some different inputs. + bool knownToBeDifferent(Id _a, Id _b); + /// Similar to @a knownToBeDifferent but require that abs(_a - b) >= 32. + bool knownToBeDifferentBy32(Id _a, Id _b); + std::string fullDAGToString(Id _id) const; private: @@ -78,6 +94,8 @@ private: /// Expression equivalence class representatives - we only store one item of an equivalence. std::vector m_representatives; + /// All expression ever encountered. + std::set m_expressions; std::vector> m_spareAssemblyItem; }; diff --git a/libevmcore/SemanticInformation.cpp b/libevmcore/SemanticInformation.cpp new file mode 100644 index 000000000..e561e7554 --- /dev/null +++ b/libevmcore/SemanticInformation.cpp @@ -0,0 +1,107 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** + * @file SemanticInformation.cpp + * @author Christian + * @date 2015 + * Helper to provide semantic information about assembly items. + */ + +#include +#include + +using namespace std; +using namespace dev; +using namespace dev::eth; + +bool SemanticInformation::breaksCSEAnalysisBlock(AssemblyItem const& _item) +{ + switch (_item.type()) + { + default: + case UndefinedItem: + case Tag: + return true; + case Push: + case PushString: + case PushTag: + case PushSub: + case PushSubSize: + case PushProgramSize: + case PushData: + return false; + case Operation: + { + if (isSwapInstruction(_item) || isDupInstruction(_item)) + return false; + if (_item.instruction() == Instruction::GAS || _item.instruction() == Instruction::PC) + return true; // GAS and PC assume a specific order of opcodes + if (_item.instruction() == Instruction::MSIZE) + return true; // msize is modified already by memory access, avoid that for now + if (_item.instruction() == Instruction::SHA3) + return true; //@todo: we have to compare sha3's not based on their memory addresses but on the memory content. + InstructionInfo info = instructionInfo(_item.instruction()); + if (_item.instruction() == Instruction::SSTORE) + return false; + if (_item.instruction() == Instruction::MSTORE) + return false; + //@todo: We do not handle the following memory instructions for now: + // calldatacopy, codecopy, extcodecopy, mstore8, + // msize (note that msize also depends on memory read access) + + // the second requirement will be lifted once it is implemented + return info.sideEffects || info.args > 2; + } + } +} + +bool SemanticInformation::isCommutativeOperation(AssemblyItem const& _item) +{ + if (_item.type() != Operation) + return false; + switch (_item.instruction()) + { + case Instruction::ADD: + case Instruction::MUL: + case Instruction::EQ: + case Instruction::AND: + case Instruction::OR: + case Instruction::XOR: + return true; + default: + return false; + } +} + +bool SemanticInformation::isDupInstruction(AssemblyItem const& _item) +{ + if (_item.type() != Operation) + return false; + return Instruction::DUP1 <= _item.instruction() && _item.instruction() <= Instruction::DUP16; +} + +bool SemanticInformation::isSwapInstruction(AssemblyItem const& _item) +{ + if (_item.type() != Operation) + return false; + return Instruction::SWAP1 <= _item.instruction() && _item.instruction() <= Instruction::SWAP16; +} + +bool SemanticInformation::isJumpInstruction(AssemblyItem const& _item) +{ + return _item == AssemblyItem(Instruction::JUMP) || _item == AssemblyItem(Instruction::JUMPI); +} diff --git a/libevmcore/SemanticInformation.h b/libevmcore/SemanticInformation.h new file mode 100644 index 000000000..7497dc651 --- /dev/null +++ b/libevmcore/SemanticInformation.h @@ -0,0 +1,50 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** + * @file SemanticInformation.h + * @author Christian + * @date 2015 + * Helper to provide semantic information about assembly items. + */ + +#pragma once + + +namespace dev +{ +namespace eth +{ + +class AssemblyItem; + +/** + * Helper functions to provide context-independent information about assembly items. + */ +struct SemanticInformation +{ + /// @returns true if the given items starts a new block for common subexpression analysis. + static bool breaksCSEAnalysisBlock(AssemblyItem const& _item); + /// @returns true if the item is a two-argument operation whose value does not depend on the + /// order of its arguments. + static bool isCommutativeOperation(AssemblyItem const& _item); + static bool isDupInstruction(AssemblyItem const& _item); + static bool isSwapInstruction(AssemblyItem const& _item); + static bool isJumpInstruction(AssemblyItem const& _item); +}; + +} +} diff --git a/libp2p/Host.cpp b/libp2p/Host.cpp index ab39c5c25..45ebd6db1 100644 --- a/libp2p/Host.cpp +++ b/libp2p/Host.cpp @@ -678,9 +678,6 @@ void Host::disconnectLatePeers() bytes Host::saveNetwork() const { - if (!m_nodeTable) - return bytes(); - std::list peers; { RecursiveGuard l(x_sessions); @@ -692,27 +689,22 @@ bytes Host::saveNetwork() const RLPStream network; int count = 0; + for (auto const& p: peers) { - RecursiveGuard l(x_sessions); - for (auto const& p: peers) + // Only save peers which have connected within 2 days, with properly-advertised port and public IP address + // todo: e2e ipv6 support + bi::tcp::endpoint endpoint(p.peerEndpoint()); + if (!endpoint.address().is_v4()) + continue; + + if (chrono::system_clock::now() - p.m_lastConnected < chrono::seconds(3600 * 48) && endpoint.port() > 0 && endpoint.port() < /*49152*/32768 && p.id != id() && !isPrivateAddress(p.endpoint.udp.address()) && !isPrivateAddress(endpoint.address())) { - // TODO: alpha: Figure out why it ever shares these ports.//p.address.port() >= 30300 && p.address.port() <= 30305 && - // TODO: alpha: if/how to save private addresses - // Only save peers which have connected within 2 days, with properly-advertised port and public IP address - if (chrono::system_clock::now() - p.m_lastConnected < chrono::seconds(3600 * 48) && p.peerEndpoint().port() > 0 && p.peerEndpoint().port() < /*49152*/32768 && p.id != id() && !isPrivateAddress(p.endpoint.udp.address()) && !isPrivateAddress(p.endpoint.tcp.address())) - { - network.appendList(10); - if (p.peerEndpoint().address().is_v4()) - network << p.peerEndpoint().address().to_v4().to_bytes(); - else - network << p.peerEndpoint().address().to_v6().to_bytes(); - // TODO: alpha: replace 0 with trust-state of node - network << p.peerEndpoint().port() << p.id << 0 - << chrono::duration_cast(p.m_lastConnected.time_since_epoch()).count() - << chrono::duration_cast(p.m_lastAttempted.time_since_epoch()).count() - << p.m_failedAttempts << (unsigned)p.m_lastDisconnect << p.m_score << p.m_rating; - count++; - } + network.appendList(10); + network << endpoint.port() << p.id << p.required + << chrono::duration_cast(p.m_lastConnected.time_since_epoch()).count() + << chrono::duration_cast(p.m_lastAttempted.time_since_epoch()).count() + << p.m_failedAttempts << (unsigned)p.m_lastDisconnect << p.m_score << p.m_rating; + count++; } } @@ -731,10 +723,13 @@ bytes Host::saveNetwork() const count++; } } + // else: TODO: use previous configuration if available RLPStream ret(3); ret << dev::p2p::c_protocolVersion << m_alias.secret(); - ret.appendList(count).appendRaw(network.out(), count); + ret.appendList(count); + if (!!count) + ret.appendRaw(network.out(), count); return ret.out(); } @@ -757,22 +752,14 @@ void Host::restoreNetwork(bytesConstRef _b) for (auto i: r[2]) { + // todo: e2e ipv6 support + // bi::tcp::endpoint(bi::address_v6(i[0].toArray()), i[1].toInt()); + if (i[0].itemCount() != 4) + continue; bi::tcp::endpoint tcp; bi::udp::endpoint udp; - if (i[0].itemCount() == 4) - { - tcp = bi::tcp::endpoint(bi::address_v4(i[0].toArray()), i[1].toInt()); - udp = bi::udp::endpoint(bi::address_v4(i[0].toArray()), i[1].toInt()); - } - else - { - tcp = bi::tcp::endpoint(bi::address_v6(i[0].toArray()), i[1].toInt()); - udp = bi::udp::endpoint(bi::address_v6(i[0].toArray()), i[1].toInt()); - } - - // skip private addresses - // todo: to support private addresseses entries must be stored - // and managed externally by host rather than nodetable. + tcp = bi::tcp::endpoint(bi::address_v4(i[0].toArray()), i[1].toInt()); + udp = bi::udp::endpoint(bi::address_v4(i[0].toArray()), i[1].toInt()); if (isPrivateAddress(tcp.address()) || isPrivateAddress(udp.address())) continue; @@ -783,6 +770,7 @@ void Host::restoreNetwork(bytesConstRef _b) { shared_ptr p = make_shared(); p->id = id; + p->required = i[3].toInt(); p->m_lastConnected = chrono::system_clock::time_point(chrono::seconds(i[4].toInt())); p->m_lastAttempted = chrono::system_clock::time_point(chrono::seconds(i[5].toInt())); p->m_failedAttempts = i[6].toInt(); @@ -792,7 +780,10 @@ void Host::restoreNetwork(bytesConstRef _b) p->endpoint.tcp = tcp; p->endpoint.udp = udp; m_peers[p->id] = p; - m_nodeTable->addNode(*p.get()); + if (p->required) + requirePeer(p->id, p->endpoint.udp.address(), p->endpoint.udp.port()); + else + m_nodeTable->addNode(*p.get()); } } } @@ -801,7 +792,7 @@ void Host::restoreNetwork(bytesConstRef _b) KeyPair Host::networkAlias(bytesConstRef _b) { RLP r(_b); - if (r.itemCount() == 3 && r[0].isInt() && r[0].toInt() == 1) + if (r.itemCount() == 3 && r[0].isInt() && r[0].toInt() == dev::p2p::c_protocolVersion) return move(KeyPair(move(Secret(r[1].toBytes())))); else return move(KeyPair::create()); diff --git a/libsolidity/AST.cpp b/libsolidity/AST.cpp index e3c9bc964..095ba7bf1 100644 --- a/libsolidity/AST.cpp +++ b/libsolidity/AST.cpp @@ -337,8 +337,14 @@ void FunctionDefinition::checkTypeRequirements() { if (!var->getType()->canLiveOutsideStorage()) BOOST_THROW_EXCEPTION(var->createTypeError("Type is required to live outside storage.")); - if (!(var->getType()->externalType()) && getVisibility() >= Visibility::Public) - BOOST_THROW_EXCEPTION(var->createTypeError("Internal type is not allowed for function with external visibility")); + if (getVisibility() >= Visibility::Public && !(var->getType()->externalType())) + { + // todo delete when will be implemented arrays as parameter type in internal functions + if (getVisibility() == Visibility::Public && var->getType()->getCategory() == Type::Category::Array) + BOOST_THROW_EXCEPTION(var->createTypeError("Arrays only implemented for external functions.")); + else + BOOST_THROW_EXCEPTION(var->createTypeError("Internal type is not allowed for public and external functions.")); + } } for (ASTPointer const& modifier: m_functionModifiers) modifier->checkTypeRequirements(isConstructor() ? @@ -379,7 +385,11 @@ void VariableDeclaration::checkTypeRequirements() m_value->expectType(*m_type); if (m_isStateVariable && !m_type->externalType() && getVisibility() >= Visibility::Public) BOOST_THROW_EXCEPTION(createTypeError("Internal type is not allowed for state variables.")); - } else + + if (!FunctionType(*this).externalType()) + BOOST_THROW_EXCEPTION(createTypeError("Internal type is not allowed for public state variables.")); + } + else { // no type declared and no previous assignment, infer the type m_value->checkTypeRequirements(); diff --git a/libsolidity/Types.cpp b/libsolidity/Types.cpp index 5fd7d24a6..78649cc95 100644 --- a/libsolidity/Types.cpp +++ b/libsolidity/Types.cpp @@ -1103,11 +1103,18 @@ TypePointer FunctionType::externalType() const TypePointers paramTypes; TypePointers retParamTypes; - for (auto it = m_parameterTypes.cbegin(); it != m_parameterTypes.cend(); ++it) - paramTypes.push_back((*it)->externalType()); - for (auto it = m_returnParameterTypes.cbegin(); it != m_returnParameterTypes.cend(); ++it) - retParamTypes.push_back((*it)->externalType()); - + for (auto type: m_parameterTypes) + { + if (!type->externalType()) + return TypePointer(); + paramTypes.push_back(type->externalType()); + } + for (auto type: m_returnParameterTypes) + { + if (!type->externalType()) + return TypePointer(); + retParamTypes.push_back(type->externalType()); + } return make_shared(paramTypes, retParamTypes, m_location, m_arbitraryParameters); } diff --git a/libsolidity/Types.h b/libsolidity/Types.h index 99fd878f0..e1852bc7f 100644 --- a/libsolidity/Types.h +++ b/libsolidity/Types.h @@ -512,6 +512,8 @@ public: virtual Category getCategory() const override { return Category::Function; } + /// @returns TypePointer of a new FunctionType object. All input/return parameters are an appropriate external types of input/return parameters of current function. + /// Returns an empty shared pointer if one of the input/return parameters does not have an externaltype. virtual TypePointer externalType() const override; explicit FunctionType(FunctionDefinition const& _function, bool _isInternal = true); diff --git a/libtestutils/CMakeLists.txt b/libtestutils/CMakeLists.txt index 4ae52e0c9..3ac4f34f8 100644 --- a/libtestutils/CMakeLists.txt +++ b/libtestutils/CMakeLists.txt @@ -32,7 +32,10 @@ endif() target_link_libraries(${EXECUTABLE} ${Boost_FILESYSTEM_LIBRARIES}) target_link_libraries(${EXECUTABLE} ${JSONCPP_LIBRARIES}) target_link_libraries(${EXECUTABLE} ethereum) -target_link_libraries(${EXECUTABLE} web3jsonrpc) + +if (JSONRPC) + target_link_libraries(${EXECUTABLE} web3jsonrpc) +endif() install( TARGETS ${EXECUTABLE} RUNTIME DESTINATION bin ARCHIVE DESTINATION lib LIBRARY DESTINATION lib ) install( FILES ${HEADERS} DESTINATION include/${EXECUTABLE} ) diff --git a/libweb3jsonrpc/WebThreeStubServerBase.cpp b/libweb3jsonrpc/WebThreeStubServerBase.cpp index cab986364..7524e7519 100644 --- a/libweb3jsonrpc/WebThreeStubServerBase.cpp +++ b/libweb3jsonrpc/WebThreeStubServerBase.cpp @@ -402,7 +402,7 @@ string WebThreeStubServerBase::eth_getCode(string const& _address, string const& { try { - return toJS(client()->codeAt(jsToAddress(_address), toBlockNumber(_blockNumber)), 1); + return toJS(client()->codeAt(jsToAddress(_address), toBlockNumber(_blockNumber))); } catch (...) { diff --git a/mix/CodeModel.cpp b/mix/CodeModel.cpp index a96ab8791..53ec34e2b 100644 --- a/mix/CodeModel.cpp +++ b/mix/CodeModel.cpp @@ -93,8 +93,23 @@ private: bool m_functionScope; uint m_storageSlot; }; + +dev::eth::AssemblyItems filterLocations(dev::eth::AssemblyItems const& _locations, dev::solidity::ContractDefinition const& _contract, QHash _functions) +{ + dev::eth::AssemblyItems result; + result.reserve(_locations.size()); + for (dev::eth::AssemblyItem item : _locations) + { + dev::SourceLocation const& l = item.getLocation(); + if (_contract.getLocation() == l || _functions.contains(LocationPair(l.start, l.end))) + item.setLocation(dev::SourceLocation(-1, -1, l.sourceName)); + result.push_back(item); + } + return result; } +} //namespace + void BackgroundWorker::queueCodeChange(int _jobId) { m_model->runCompilationJob(_jobId); @@ -105,13 +120,11 @@ CompiledContract::CompiledContract(const dev::solidity::CompilerStack& _compiler m_sourceHash(qHash(_source)) { std::string name = _contractName.toStdString(); - auto const& contractDefinition = _compiler.getContractDefinition(name); + ContractDefinition const& contractDefinition = _compiler.getContractDefinition(name); m_contract.reset(new QContractDefinition(nullptr, &contractDefinition)); QQmlEngine::setObjectOwnership(m_contract.get(), QQmlEngine::CppOwnership); m_contract->moveToThread(QApplication::instance()->thread()); m_bytes = _compiler.getBytecode(_contractName.toStdString()); - m_assemblyItems = _compiler.getRuntimeAssemblyItems(name); - m_constructorAssemblyItems = _compiler.getAssemblyItems(name); dev::solidity::InterfaceHandler interfaceHandler; m_contractInterface = QString::fromStdString(*interfaceHandler.getABIInterface(contractDefinition)); @@ -122,6 +135,8 @@ CompiledContract::CompiledContract(const dev::solidity::CompilerStack& _compiler CollectDeclarationsVisitor visitor(&m_functions, &m_locals, &m_storage); contractDefinition.accept(visitor); + m_assemblyItems = filterLocations(_compiler.getRuntimeAssemblyItems(name), contractDefinition, m_functions); + m_constructorAssemblyItems = filterLocations(_compiler.getAssemblyItems(name), contractDefinition, m_functions); } QString CompiledContract::codeHex() const diff --git a/mix/qml.qrc b/mix/qml.qrc index bed954741..5ceaaced6 100644 --- a/mix/qml.qrc +++ b/mix/qml.qrc @@ -61,5 +61,6 @@ qml/js/TransactionHelper.js qml/main.qml qml/qmldir + qml/StatesComboBox.qml diff --git a/mix/qml/CodeEditorView.qml b/mix/qml/CodeEditorView.qml index b100dffd0..25ecbc98c 100644 --- a/mix/qml/CodeEditorView.qml +++ b/mix/qml/CodeEditorView.qml @@ -11,6 +11,7 @@ Item { signal breakpointsChanged(string documentId) signal isCleanChanged(var isClean, string documentId) + function getDocumentText(documentId) { for (var i = 0; i < editorListModel.count; i++) { if (editorListModel.get(i).documentId === documentId) { diff --git a/mix/qml/Debugger.qml b/mix/qml/Debugger.qml index 358750b24..836bbaf94 100644 --- a/mix/qml/Debugger.qml +++ b/mix/qml/Debugger.qml @@ -219,6 +219,33 @@ Rectangle { anchors.horizontalCenter: parent.horizontalCenter id: jumpButtons spacing: 3 + + StepActionImage + { + id: playAction + enabledStateImg: "qrc:/qml/img/play_button.png" + disableStateImg: "qrc:/qml/img/play_button.png" + onClicked: projectModel.stateListModel.runState(transactionLog.selectedStateIndex) + width: 30 + height: 30 + buttonShortcut: "Ctrl+Shift+F8" + buttonTooltip: qsTr("Start Debugging") + visible: true + } + + StepActionImage + { + id: pauseAction + enabledStateImg: "qrc:/qml/img/stop_button2x.png" + disableStateImg: "qrc:/qml/img/stop_button2x.png" + onClicked: Debugger.init(null); + width: 30 + height: 30 + buttonShortcut: "Ctrl+Shift+F9" + buttonTooltip: qsTr("Stop Debugging") + visible: true + } + StepActionImage { id: runBackAction; diff --git a/mix/qml/LogsPane.qml b/mix/qml/LogsPane.qml index 3601483a0..ca123f6a0 100644 --- a/mix/qml/LogsPane.qml +++ b/mix/qml/LogsPane.qml @@ -7,81 +7,258 @@ import "." Rectangle { + property variant currentStatus; function push(_level, _type, _content) { _content = _content.replace(/\n/g, " ") - logsModel.insert(0, { "type": _type, "date": Qt.formatDateTime(new Date(), "hh:mm:ss dd.MM.yyyy"), "content": _content, "level": _level }); + logsModel.insert(0, { "type": _type, "date": Qt.formatDateTime(new Date(), "hh:mm:ss"), "content": _content, "level": _level }); + } + + onVisibleChanged: + { + if (visible && (logsModel.count === 0 || (logsModel.get(0).date !== currentStatus.date && logsModel.get(0).content !== currentStatus.content))) + logsModel.insert(0, { "type": currentStatus.type, "date": currentStatus.date, "content": currentStatus.content, "level": currentStatus.level }); + else if (!visible) + { + for (var k = 0; k < logsModel.count; k++) + { + if (logsModel.get(k).type === "Comp") //do not keep compilation logs. + logsModel.remove(k); + } + } } anchors.fill: parent - radius: 5 - color: LogsPaneStyle.generic.layout.backgroundColor - border.color: LogsPaneStyle.generic.layout.borderColor - border.width: LogsPaneStyle.generic.layout.borderWidth - ColumnLayout { + radius: 10 + color: "transparent" + id: logsPane + Column { z: 2 - height: parent.height + height: parent.height - rowAction.height width: parent.width spacing: 0 + ListModel { + id: logsModel + } + + ScrollView + { + id: scrollView + height: parent.height + width: parent.width + horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff + Column + { + id: logsRect + spacing: 0 + Repeater { + id: logsRepeater + clip: true + property string frontColor: "transparent" + model: SortFilterProxyModel { + id: proxyModel + source: logsModel + property var roles: ["-", "javascript", "run", "state", "comp"] + + Component.onCompleted: { + filterType = regEx(proxyModel.roles); + } + + function search(_value) + { + filterContent = _value; + } + + function toogleFilter(_value) + { + var count = roles.length; + for (var i in roles) + { + if (roles[i] === _value) + { + roles.splice(i, 1); + break; + } + } + if (count === roles.length) + roles.push(_value); + + filterType = regEx(proxyModel.roles); + } + + function regEx(_value) + { + return "(?:" + roles.join('|') + ")"; + } + + filterType: "(?:javascript|run|state|comp)" + filterContent: "" + filterSyntax: SortFilterProxyModel.RegExp + filterCaseSensitivity: Qt.CaseInsensitive + } + + Rectangle + { + width: LogsPaneStyle.generic.layout.dateWidth + LogsPaneStyle.generic.layout.contentWidth + LogsPaneStyle.generic.layout.typeWidth + height: 30 + color: + { + var cl; + if (level === "warning" || level === "error") + cl = LogsPaneStyle.generic.layout.errorColor; + else + cl = index % 2 === 0 ? "transparent" : LogsPaneStyle.generic.layout.logAlternateColor; + if (index === 0) + logsRepeater.frontColor = cl; + return cl; + } + + + MouseArea + { + anchors.fill: parent + onClicked: + { + if (logContent.elide === Text.ElideNone) + { + logContent.elide = Text.ElideRight; + logContent.wrapMode = Text.NoWrap + parent.height = 30; + } + else + { + logContent.elide = Text.ElideNone; + logContent.wrapMode = Text.WordWrap; + parent.height = logContent.lineCount * 30; + } + } + } + + + DefaultLabel { + text: date; + font.family: LogsPaneStyle.generic.layout.logLabelFont + width: LogsPaneStyle.generic.layout.dateWidth + font.pointSize: Style.absoluteSize(-1) + anchors.left: parent.left + anchors.leftMargin: 15 + anchors.verticalCenter: parent.verticalCenter + color: { + parent.getColor(level); + } + } + + DefaultLabel { + text: type; + font.family: LogsPaneStyle.generic.layout.logLabelFont + width: LogsPaneStyle.generic.layout.typeWidth + font.pointSize: Style.absoluteSize(-1) + anchors.left: parent.left + anchors.leftMargin: 100 + anchors.verticalCenter: parent.verticalCenter + color: { + parent.getColor(level); + } + } + + Text { + id: logContent + text: content; + font.family: LogsPaneStyle.generic.layout.logLabelFont + width: LogsPaneStyle.generic.layout.contentWidth + font.pointSize: Style.absoluteSize(-1) + anchors.verticalCenter: parent.verticalCenter + elide: Text.ElideRight + anchors.left: parent.left + anchors.leftMargin: 230 + color: { + parent.getColor(level); + } + } + + function getColor() + { + if (level === "error") + return "red"; + else if (level === "warning") + return "orange"; + else + return "#808080"; + } + } + } + } + } + + Component { + id: itemDelegate + DefaultLabel { + text: styleData.value; + font.family: LogsPaneStyle.generic.layout.logLabelFont + font.pointSize: Style.absoluteSize(-1) + color: { + if (proxyModel.get(styleData.row).level === "error") + return "red"; + else if (proxyModel.get(styleData.row).level === "warning") + return "orange"; + else + return "#808080"; + } + } + } + } + + Rectangle + { + gradient: Gradient { + GradientStop { position: 0.0; color: "#f1f1f1" } + GradientStop { position: 1.0; color: "#d9d7da" } + } + Layout.preferredHeight: LogsPaneStyle.generic.layout.headerHeight + height: LogsPaneStyle.generic.layout.headerHeight + width: logsPane.width + anchors.bottom: parent.bottom Row { id: rowAction - Layout.preferredHeight: LogsPaneStyle.generic.layout.headerHeight - height: LogsPaneStyle.generic.layout.headerHeight anchors.leftMargin: LogsPaneStyle.generic.layout.leftMargin anchors.left: parent.left spacing: LogsPaneStyle.generic.layout.headerButtonSpacing - Button + height: parent.height + Rectangle { - height: LogsPaneStyle.generic.layout.headerButtonHeight - anchors.verticalCenter: parent.verticalCenter - action: clearAction - iconSource: "qrc:/qml/img/broom.png" - } - - Action { - id: clearAction - enabled: logsModel.count > 0 - tooltip: qsTr("Clear") - onTriggered: { - logsModel.clear() + color: "transparent" + height: parent.height + width: 40 + DefaultLabel + { + anchors.verticalCenter: parent.verticalCenter + color: LogsPaneStyle.generic.layout.logLabelColor + font.pointSize: Style.absoluteSize(-3) + font.family: LogsPaneStyle.generic.layout.logLabelFont + text: qsTr("Show:") } } - Button - { - height: LogsPaneStyle.generic.layout.headerButtonHeight + Rectangle { anchors.verticalCenter: parent.verticalCenter - action: copytoClipBoardAction - iconSource: "qrc:/qml/img/copy.png" - } - - Action { - id: copytoClipBoardAction - enabled: logsModel.count > 0 - tooltip: qsTr("Copy to Clipboard") - onTriggered: { - var content = ""; - for (var k = 0; k < logsModel.count; k++) - { - var log = logsModel.get(k); - content += log.type + "\t" + log.level + "\t" + log.date + "\t" + log.content + "\n"; - } - clipboard.text = content; - } + width: 1; + height: parent.height + color: LogsPaneStyle.generic.layout.buttonSeparatorColor1 } Rectangle { anchors.verticalCenter: parent.verticalCenter - width: 1; - height: parent.height - 10 - color : "#808080" + width: 2; + height: parent.height + color: LogsPaneStyle.generic.layout.buttonSeparatorColor2 } ToolButton { id: javascriptButton checkable: true height: LogsPaneStyle.generic.layout.headerButtonHeight + width: 20 anchors.verticalCenter: parent.verticalCenter checked: true onCheckedChanged: { @@ -97,16 +274,35 @@ Rectangle font.pointSize: Style.absoluteSize(-3) color: LogsPaneStyle.generic.layout.logLabelColor anchors.centerIn: parent - text: qsTr("JavaScript") + text: qsTr("JS") } } + background: + Rectangle { + color: javascriptButton.checked ? LogsPaneStyle.generic.layout.buttonSelected : "transparent" + } } } + Rectangle { + anchors.verticalCenter: parent.verticalCenter + width: 1; + height: parent.height + color: LogsPaneStyle.generic.layout.buttonSeparatorColor1 + } + + Rectangle { + anchors.verticalCenter: parent.verticalCenter + width: 2; + height: parent.height + color: LogsPaneStyle.generic.layout.buttonSeparatorColor2 + } + ToolButton { id: runButton checkable: true height: LogsPaneStyle.generic.layout.headerButtonHeight + width: 30 anchors.verticalCenter: parent.verticalCenter checked: true onCheckedChanged: { @@ -125,14 +321,33 @@ Rectangle text: qsTr("Run") } } + background: + Rectangle { + color: runButton.checked ? LogsPaneStyle.generic.layout.buttonSelected : "transparent" + } } } + Rectangle { + anchors.verticalCenter: parent.verticalCenter + width: 1; + height: parent.height + color: LogsPaneStyle.generic.layout.buttonSeparatorColor1 + } + + Rectangle { + anchors.verticalCenter: parent.verticalCenter + width: 2; + height: parent.height + color: LogsPaneStyle.generic.layout.buttonSeparatorColor2 + } + ToolButton { id: stateButton checkable: true height: LogsPaneStyle.generic.layout.headerButtonHeight anchors.verticalCenter: parent.verticalCenter + width: 35 checked: true onCheckedChanged: { proxyModel.toogleFilter("state") @@ -145,133 +360,226 @@ Rectangle DefaultLabel { font.family: LogsPaneStyle.generic.layout.logLabelFont font.pointSize: Style.absoluteSize(-3) - color: "#5391d8" + color: LogsPaneStyle.generic.layout.logLabelColor anchors.centerIn: parent text: qsTr("State") } } + background: + Rectangle { + color: stateButton.checked ? LogsPaneStyle.generic.layout.buttonSelected : "transparent" + } } } - DefaultTextField - { - id: searchBox - height: LogsPaneStyle.generic.layout.headerButtonHeight + Rectangle { anchors.verticalCenter: parent.verticalCenter - width: LogsPaneStyle.generic.layout.headerInputWidth - font.family: LogsPaneStyle.generic.layout.logLabelFont - font.pointSize: Style.absoluteSize(-3) - font.italic: true - onTextChanged: { - proxyModel.search(text); - } + width: 1; + height: parent.height + color: LogsPaneStyle.generic.layout.buttonSeparatorColor1 } - } - ListModel { - id: logsModel + Rectangle { + anchors.verticalCenter: parent.verticalCenter + width: 2; + height: parent.height + color: LogsPaneStyle.generic.layout.buttonSeparatorColor2 + } } - TableView { - id: logsTable - clip: true - Layout.fillWidth: true - Layout.preferredHeight: parent.height - rowAction.height - headerVisible : false - onDoubleClicked: + Row + { + height: parent.height + anchors.right: parent.right + anchors.rightMargin: 10 + spacing: 10 + Rectangle { - var log = logsModel.get(logsTable.currentRow); - if (log) - clipboard.text = (log.type + "\t" + log.level + "\t" + log.date + "\t" + log.content); - } + height: LogsPaneStyle.generic.layout.headerButtonHeight + anchors.verticalCenter: parent.verticalCenter + color: "transparent" + width: 20 + Button + { + id: clearButton + action: clearAction + anchors.fill: parent + anchors.verticalCenter: parent.verticalCenter + height: 25 + style: + ButtonStyle { + background: + Rectangle { + height: LogsPaneStyle.generic.layout.headerButtonHeight + implicitHeight: LogsPaneStyle.generic.layout.headerButtonHeight + color: "transparent" + } + } + } - model: SortFilterProxyModel { - id: proxyModel - source: logsModel - property var roles: ["-", "javascript", "run", "state"] + Image { + id: clearImage + source: clearAction.enabled ? "qrc:/qml/img/cleariconactive.png" : "qrc:/qml/img/clearicon.png" + anchors.centerIn: parent + fillMode: Image.PreserveAspectFit + width: 20 + height: 25 + } - Component.onCompleted: { - filterType = regEx(proxyModel.roles); + Action { + id: clearAction + enabled: logsModel.count > 0 + tooltip: qsTr("Clear") + onTriggered: { + logsModel.clear(); + } } + } - function search(_value) + Rectangle + { + height: LogsPaneStyle.generic.layout.headerButtonHeight + anchors.verticalCenter: parent.verticalCenter + color: "transparent" + width: 20 + Button { - filterContent = _value; + id: copyButton + action: copyAction + anchors.fill: parent + anchors.verticalCenter: parent.verticalCenter + height: 25 + style: + ButtonStyle { + background: + Rectangle { + height: LogsPaneStyle.generic.layout.headerButtonHeight + implicitHeight: LogsPaneStyle.generic.layout.headerButtonHeight + color: "transparent" + } + } } - function toogleFilter(_value) - { - var count = roles.length; - for (var i in roles) - { - if (roles[i] === _value) + Image { + id: copyImage + source: copyAction.enabled ? "qrc:/qml/img/copyiconactive.png" : "qrc:/qml/img/copyicon.png" + anchors.centerIn: parent + fillMode: Image.PreserveAspectFit + width: 20 + height: 25 + } + + Action { + id: copyAction + enabled: logsModel.count > 0 + tooltip: qsTr("Copy to Clipboard") + onTriggered: { + var content = ""; + for (var k = 0; k < logsModel.count; k++) { - roles.splice(i, 1); - break; + var log = logsModel.get(k); + content += log.type + "\t" + log.level + "\t" + log.date + "\t" + log.content + "\n"; } + clipboard.text = content; } - if (count === roles.length) - roles.push(_value); - - filterType = regEx(proxyModel.roles); } + } - function regEx(_value) + Rectangle + { + width: 120 + radius: 10 + height: 25 + color: "white" + anchors.verticalCenter: parent.verticalCenter + + Image { - return "(?:" + roles.join('|') + ")"; + id: searchImg + source: "qrc:/qml/img/searchicon.png" + fillMode: Image.PreserveAspectFit + width: 20 + height: 25 + z: 3 } - filterType: "(?:javascript|run|state)" - filterContent: "" - filterSyntax: SortFilterProxyModel.RegExp - filterCaseSensitivity: Qt.CaseInsensitive - } - TableViewColumn - { - role: "date" - title: qsTr("date") - width: LogsPaneStyle.generic.layout.dateWidth - delegate: itemDelegate - } - TableViewColumn - { - role: "type" - title: qsTr("type") - width: LogsPaneStyle.generic.layout.typeWidth - delegate: itemDelegate + DefaultTextField + { + id: searchBox + z: 2 + width: 100 + anchors.left: searchImg.right + anchors.leftMargin: -7 + font.family: LogsPaneStyle.generic.layout.logLabelFont + font.pointSize: Style.absoluteSize(-3) + font.italic: true + text: qsTr(" - Search - ") + onFocusChanged: + { + if (!focus && text === "") + text = qsTr(" - Search - "); + else if (focus && text === qsTr(" - Search - ")) + text = ""; + } + + onTextChanged: { + if (text === qsTr(" - Search - ")) + proxyModel.search(""); + else + proxyModel.search(text); + } + + style: + TextFieldStyle { + background: Rectangle { + radius: 10 + } + } + } } - TableViewColumn + + Rectangle { - role: "content" - title: qsTr("content") - width: LogsPaneStyle.generic.layout.contentWidth - delegate: itemDelegate - } + height: LogsPaneStyle.generic.layout.headerButtonHeight + anchors.verticalCenter: parent.verticalCenter + color: "transparent" + width: 20 + Button + { + id: hideButton + action: hideAction + anchors.fill: parent + anchors.verticalCenter: parent.verticalCenter + height: 25 + style: + ButtonStyle { + background: + Rectangle { + height: LogsPaneStyle.generic.layout.headerButtonHeight + implicitHeight: LogsPaneStyle.generic.layout.headerButtonHeight + color: "transparent" + } + } + } - rowDelegate: Item { - Rectangle { - width: logsTable.width - 4 - height: 17 - color: styleData.alternate ? "transparent" : LogsPaneStyle.generic.layout.logAlternateColor + Image { + id: hideImage + source: "qrc:/qml/img/exit.png" + anchors.centerIn: parent + fillMode: Image.PreserveAspectFit + width: 20 + height: 25 } - } - } - Component { - id: itemDelegate - DefaultLabel { - text: styleData.value; - font.family: LogsPaneStyle.generic.layout.logLabelFont - font.pointSize: Style.absoluteSize(-1) - color: { - if (proxyModel.get(styleData.row).level === "error") - return "red"; - else if (proxyModel.get(styleData.row).level === "warning") - return "orange"; - else - return "#808080"; + Action { + id: hideAction + tooltip: qsTr("Exit") + onTriggered: { + logsPane.parent.toggle(); + } } } } } + } diff --git a/mix/qml/LogsPaneStyle.qml b/mix/qml/LogsPaneStyle.qml index 59b80653a..0bbdfcee4 100644 --- a/mix/qml/LogsPaneStyle.qml +++ b/mix/qml/LogsPaneStyle.qml @@ -11,19 +11,21 @@ QtObject { property QtObject generic: QtObject { property QtObject layout: QtObject { property string backgroundColor: "#f7f7f7" - property string borderColor: "#5391d8" - property int borderWidth: 1 - property int headerHeight: 35 - property int headerButtonSpacing: 5 + property int headerHeight: 30 + property int headerButtonSpacing: 0 property int leftMargin: 10 property int headerButtonHeight: 30 - property string logLabelColor: "#5391d8" + property string logLabelColor: "#4a4a4a" property string logLabelFont: "sans serif" property int headerInputWidth: 200 property int dateWidth: 150 - property int typeWidth: 80 - property int contentWidth: 700 - property string logAlternateColor: "#f0f0f0" + property int typeWidth: 150 + property int contentWidth: 560 + property string logAlternateColor: "#f6f5f6" + property string errorColor: "#fffcd5" + property string buttonSeparatorColor1: "#d3d0d0" + property string buttonSeparatorColor2: "#f2f1f2" + property string buttonSelected: "#dcdcdc" } } } diff --git a/mix/qml/ProjectModel.qml b/mix/qml/ProjectModel.qml index f4b73b601..ec7681f16 100644 --- a/mix/qml/ProjectModel.qml +++ b/mix/qml/ProjectModel.qml @@ -64,6 +64,7 @@ Item { function renameDocument(documentId, newName) { ProjectModelCode.renameDocument(documentId, newName); } function removeDocument(documentId) { ProjectModelCode.removeDocument(documentId); } function getDocument(documentId) { return ProjectModelCode.getDocument(documentId); } + function getDocumentIdByName(documentName) { return ProjectModelCode.getDocumentIdByName(documentName); } function getDocumentIndex(documentId) { return ProjectModelCode.getDocumentIndex(documentId); } function addExistingFiles(paths) { ProjectModelCode.doAddExistingFiles(paths); } function deployProject() { ProjectModelCode.deployProject(false); } diff --git a/mix/qml/StatesComboBox.qml b/mix/qml/StatesComboBox.qml new file mode 100644 index 000000000..bc7a4853d --- /dev/null +++ b/mix/qml/StatesComboBox.qml @@ -0,0 +1,260 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file StatesComboBox.qml + * @author Ali Mashatan ali@ethdev.com + * @date 2015 + * Ethereum IDE client. + */ + +import QtQuick 2.0 +import QtQuick.Controls 1.0 +import QtQuick.Layouts 1.1 +import QtGraphicalEffects 1.0 + +Rectangle { + id: statesComboBox + + width: 200 + height: 23 + + Component.onCompleted: { + var top = dropDownList + while (top.parent) { + top = top.parent + if (top.objectName == "debugPanel") + break + } + var coordinates = dropDownList.mapToItem(top, 0, 0) + //the order is important + dropDownShowdowList.parent = top + dropDownList.parent = top + + dropDownShowdowList.x = coordinates.x + dropDownShowdowList.y = coordinates.y + + dropDownList.x = coordinates.x + dropDownList.y = coordinates.y + } + + signal selectItem(real item) + signal editItem(real item) + signal selectCreate + property int rowHeight: 25 + property variant items + property alias selectedItem: chosenItemText.text + property alias selectedIndex: listView.currentRow + function setSelectedIndex(index) { + listView.currentRow = index + chosenItemText.text = statesComboBox.items.get(index).title + } + + signal comboClicked + + property variant colorItem + property variant colorSelect + + SourceSansProRegular + { + id: regularFont + } + + SourceSansProBold + { + id: boldFont + } + + smooth: true + Rectangle { + id: chosenItem + width: parent.width + height: statesComboBox.height + color: statesComboBox.color + + Text { + id: chosenItemText + anchors.left: parent.left + anchors.leftMargin: 10 + anchors.verticalCenter: parent.verticalCenter + color: statesComboBox.colorItem + text: "" + font.family: regularFont.name + } + + MouseArea { + anchors.fill: parent + onClicked: { + statesComboBox.state = statesComboBox.state === "dropDown" ? "" : "dropDown" + } + } + } + + Rectangle { + id: dropDownShowdowList + width: statesComboBox.width + opacity: 0.3 + height: 0 + clip: true + radius: 4 + anchors.top: chosenItem.top + anchors.margins: 2 + color: "gray" + } + //ToDo: We need scrollbar for items + Rectangle { + id: dropDownList + width: statesComboBox.width + height: 0 + clip: true + radius: 4 + anchors.top: chosenItem.top + anchors.topMargin: 23 + color: statesComboBox.color + + ColumnLayout { + spacing: 2 + TableView { + id: listView + height: 20 + implicitHeight: 0 + width: statesComboBox.width + model: statesComboBox.items + horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff + currentRow: -1 + headerVisible: false + backgroundVisible: false + alternatingRowColors: false + frameVisible: false + + TableViewColumn { + role: "title" + title: "" + width: statesComboBox.width + delegate: mainItemDelegate + } + rowDelegate: Rectangle { + width: statesComboBox.width + height: statesComboBox.rowHeight + } + Component { + id: mainItemDelegate + Rectangle { + id: itemDelegate + width: statesComboBox.width + height: statesComboBox.height + Text { + id: textItemid + text: styleData.value + color: statesComboBox.colorItem + anchors.top: parent.top + anchors.left: parent.left + anchors.leftMargin: 10 + anchors.topMargin: 5 + font.family: regularFont.name + } + Image { + id: imageItemid + height: 20 + width: 20 + anchors.right: parent.right + anchors.top: parent.top + anchors.margins: 5 + visible: false + fillMode: Image.PreserveAspectFit + source: "img/edit_combox.png" + } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + + onEntered: { + imageItemid.visible = true + textItemid.color = statesComboBox.colorSelect + } + onExited: { + imageItemid.visible = false + textItemid.color = statesComboBox.colorItem + } + onClicked: { + if (mouseX > imageItemid.x + && mouseX < imageItemid.x + imageItemid.width + && mouseY > imageItemid.y + && mouseY < imageItemid.y + imageItemid.height) + statesComboBox.editItem(styleData.row) + else { + statesComboBox.state = "" + var prevSelection = chosenItemText.text + chosenItemText.text = styleData.value + listView.currentRow = styleData.row + statesComboBox.selectItem(styleData.row) + } + } + } + } //Item + } //Component + } //Table View + + RowLayout { + anchors.top: listView.bottom + anchors.topMargin: 4 + anchors.left: parent.left + anchors.leftMargin: 10 + Text { + id: createStateText + width: statesComboBox.width + height: statesComboBox.height + font.family: boldFont.name + color: "#808080" + text: qsTr("Create State ...") + font.weight: Font.DemiBold + MouseArea { + anchors.fill: parent + hoverEnabled: true + + onEntered: { + createStateText.color = statesComboBox.colorSelect + } + onExited: { + createStateText.color = statesComboBox.colorItem + } + onClicked: { + statesComboBox.state = "" + statesComboBox.selectCreate() + } + } + } + } + } + } + states: State { + name: "dropDown" + PropertyChanges { + target: dropDownList + height: (statesComboBox.rowHeight * (statesComboBox.items.count + 1)) + } + PropertyChanges { + target: dropDownShowdowList + width: statesComboBox.width + 3 + height: (statesComboBox.rowHeight * (statesComboBox.items.count + 1)) + 3 + } + PropertyChanges { + target: listView + height: 20 + implicitHeight: (statesComboBox.rowHeight * (statesComboBox.items.count)) + } + } +} diff --git a/mix/qml/StatusPane.qml b/mix/qml/StatusPane.qml index 70a4b1d30..6d8042add 100644 --- a/mix/qml/StatusPane.qml +++ b/mix/qml/StatusPane.qml @@ -9,7 +9,7 @@ Rectangle { id: statusHeader objectName: "statusPane" property variant webPreview - + property alias currentStatus: logPane.currentStatus function updateStatus(message) { if (!message) @@ -17,6 +17,7 @@ Rectangle { status.state = ""; status.text = qsTr("Compile successfully."); debugImg.state = "active"; + currentStatus = { "type": "Comp", "date": Qt.formatDateTime(new Date(), "hh:mm:ss"), "content": status.text, "level": "info" } } else { @@ -24,6 +25,7 @@ Rectangle { var errorInfo = ErrorLocationFormater.extractErrorInfo(message, true); status.text = errorInfo.errorLocation + " " + errorInfo.errorDetail; debugImg.state = ""; + currentStatus = { "type": "Comp", "date": Qt.formatDateTime(new Date(), "hh:mm:ss"), "content": status.text, "level": "error" } } debugRunActionIcon.enabled = codeModel.hasContract; } @@ -33,6 +35,7 @@ Rectangle { status.state = ""; status.text = text logPane.push("info", type, text); + currentStatus = { "type": type, "date": Qt.formatDateTime(new Date(), "hh:mm:ss"), "content": text, "level": "info" } } function warningMessage(text, type) @@ -40,13 +43,15 @@ Rectangle { status.state = "warning"; status.text = text logPane.push("warning", type, text); + currentStatus = { "type": type, "date": Qt.formatDateTime(new Date(), "hh:mm:ss"), "content": text, "level": "warning" } } function errorMessage(text, type) { status.state = "error"; - status.text = text + status.text = text; logPane.push("error", type, text); + currentStatus = { "type": type, "date": Qt.formatDateTime(new Date(), "hh:mm:ss"), "content": text, "level": "error" } } Connections { @@ -76,7 +81,7 @@ Rectangle { function format(_message) { var formatted = _message.match(/(?:)/); - if (formatted) + if (!formatted) formatted = _message.match(/(?:)/); if (formatted && formatted.length > 1) formatted = formatted[1]; @@ -90,6 +95,7 @@ Rectangle { return formatted; } } + Connections { target:projectModel onDeploymentStarted: infoMessage(qsTr("Running deployment..."), "Deployment"); @@ -111,32 +117,13 @@ Rectangle { anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter radius: 3 - width: 500 + width: 600 height: 30 color: "#fcfbfc" - states: [ - State { - name: "logsOpened" - PropertyChanges { - target: statusContainer - border.color: "#5391d8" - border.width: 1 - } - }, - State { - name: "logsClosed" - PropertyChanges { - target: statusContainer - border.color: "#5391d8" - border.width: 0 - } - } - ] - Text { anchors.verticalCenter: parent.verticalCenter anchors.horizontalCenter: parent.horizontalCenter - font.pointSize: StatusPaneStyle.general.statusFontSize + font.pointSize: Style.absoluteSize(-1) height: 15 font.family: "sans serif" objectName: "status" @@ -215,40 +202,39 @@ Rectangle { { if (logsContainer.state === "opened") { - statusContainer.state = "logsClosed"; logsContainer.state = "closed" } else { - statusContainer.state = "logsOpened"; logsContainer.state = "opened"; logsContainer.focus = true; forceActiveFocus(); + calCoord(); } } id: logsContainer - width: 1000 - height: 0 - anchors.topMargin: 10 + width: 750 anchors.top: statusContainer.bottom - anchors.horizontalCenter: parent.horizontalCenter + anchors.topMargin: 4 visible: false - radius: 5 - Component.onCompleted: + radius: 10 + + function calCoord() { var top = logsContainer; while (top.parent) top = top.parent - var coordinates = logsContainer.mapToItem(top, 0, 0) + var coordinates = logsContainer.mapToItem(top, 0, 0); logsContainer.parent = top; - logsContainer.x = coordinates.x - logsContainer.y = coordinates.y + logsContainer.x = status.x + statusContainer.x - LogsPaneStyle.generic.layout.dateWidth - LogsPaneStyle.generic.layout.typeWidth + 70 } + LogsPane { - id: logPane + id: logPane; } + states: [ State { name: "opened"; @@ -257,12 +243,13 @@ Rectangle { State { name: "closed"; PropertyChanges { target: logsContainer; height: 0; visible: false } + PropertyChanges { target: statusContainer; width: 600; height: 30 } } ] transitions: Transition { NumberAnimation { properties: "height"; easing.type: Easing.InOutQuad; duration: 200 } - NumberAnimation { properties: "visible"; easing.type: Easing.InOutQuad; duration: 200 } - } + NumberAnimation { target: logsContainer; properties: "visible"; easing.type: Easing.InOutQuad; duration: 200 } + } } } diff --git a/mix/qml/TransactionLog.qml b/mix/qml/TransactionLog.qml index 43736a89a..5668c6e05 100644 --- a/mix/qml/TransactionLog.qml +++ b/mix/qml/TransactionLog.qml @@ -6,30 +6,16 @@ import QtQuick.Layouts 1.1 import org.ethereum.qml.RecordLogEntry 1.0 Item { - property ListModel fullModel: ListModel{} property ListModel transactionModel: ListModel{} property ListModel callModel: ListModel{} - - Action { - id: addStateAction - text: "Add State" - shortcut: "Ctrl+Alt+T" - enabled: codeModel.hasContract && !clientModel.running; - onTriggered: projectModel.stateListModel.addState(); - } - Action { - id: editStateAction - text: "Edit State" - shortcut: "Ctrl+Alt+T" - enabled: codeModel.hasContract && !clientModel.running && statesCombo.currentIndex >= 0 && projectModel.stateListModel.count > 0; - onTriggered: projectModel.stateListModel.editState(statesCombo.currentIndex); - } + property int selectedStateIndex: statesCombo.selectedIndex ColumnLayout { anchors.fill: parent RowLayout { - + anchors.right: parent.right + anchors.left: parent.left Connections { id: compilationStatus @@ -61,36 +47,24 @@ Item { } } - ComboBox { + StatesComboBox + { id: statesCombo - model: projectModel.stateListModel - width: 150 - editable: false - textRole: "title" - onActivated: { - model.runState(index); - } + items: projectModel.stateListModel + onSelectCreate: projectModel.stateListModel.addState(); + onEditItem: projectModel.stateListModel.editState(item) + colorItem: "#808080" + colorSelect: "#4a90e2" + color: "white" Connections { target: projectModel.stateListModel onStateRun: { - if (statesCombo.currentIndex !== index) - statesCombo.currentIndex = index; + if (statesCombo.selectedIndex !== index) + statesCombo.setSelectedIndex( index ); } } } Button - { - anchors.rightMargin: 9 - anchors.verticalCenter: parent.verticalCenter - action: editStateAction - } - Button - { - anchors.rightMargin: 9 - anchors.verticalCenter: parent.verticalCenter - action: addStateAction - } - Button { anchors.rightMargin: 9 anchors.verticalCenter: parent.verticalCenter @@ -163,16 +137,11 @@ Item { Keys.onPressed: { if ((event.modifiers & Qt.ControlModifier) && event.key === Qt.Key_C && currentRow >=0 && currentRow < logTable.model.count) { var item = logTable.model.get(currentRow); - clipboard.text = item.returned; + appContext.toClipboard(item.returned); } } } - Rectangle { - height: 6 - color: "transparent" - } } - Connections { target: clientModel onStateCleared: { diff --git a/mix/qml/WebPreview.qml b/mix/qml/WebPreview.qml index 8507c9b46..768d188cb 100644 --- a/mix/qml/WebPreview.qml +++ b/mix/qml/WebPreview.qml @@ -155,18 +155,19 @@ Item { //document request if (urlPath === "/") urlPath = "/index.html"; - var documentId = urlPath.substr(urlPath.lastIndexOf("/") + 1); + var documentName = urlPath.substr(urlPath.lastIndexOf("/") + 1); + var documentId = projectModel.getDocumentIdByName(documentName); var content = ""; if (projectModel.codeEditor.isDocumentOpen(documentId)) content = projectModel.codeEditor.getDocumentText(documentId); else { var doc = projectModel.getDocument(documentId); - if (doc !== undefined) + if (doc) content = fileIo.readFile(doc.path); } - if (documentId === urlInput.text.replace(httpServer.url + "/", "")) { + if (documentName === urlInput.text.replace(httpServer.url + "/", "")) { //root page, inject deployment script content = "\n" + content; _request.setResponseContentType("text/html"); diff --git a/mix/qml/html/codeeditor.js b/mix/qml/html/codeeditor.js index 299691ec0..d61d6e51f 100644 --- a/mix/qml/html/codeeditor.js +++ b/mix/qml/html/codeeditor.js @@ -22,17 +22,11 @@ editor.on("change", function(eMirror, object) { }); var mac = /Mac/.test(navigator.platform); +var extraKeys = {}; if (mac === true) { - editor.setOption("extraKeys", { - "Cmd-V": function(cm) { - cm.replaceSelection(clipboard); - }, - "Cmd-X": function(cm) { - window.document.execCommand("cut"); - }, - "Cmd-C": function(cm) { - window.document.execCommand("copy"); - }}); + extraKeys["Cmd-V"] = function(cm) { cm.replaceSelection(clipboard); }; + extraKeys["Cmd-X"] = function(cm) { window.document.execCommand("cut"); }; + extraKeys["Cmd-C"] = function(cm) { window.document.execCommand("copy"); }; } makeMarker = function() { @@ -102,16 +96,14 @@ setMode = function(mode) { if (mode === "javascript") { ternServer = new CodeMirror.TernServer({defs: [ ecma5Spec() ]}); - editor.setOption("extraKeys", { - "Ctrl-Space": function(cm) { ternServer.complete(cm); }, - "Ctrl-I": function(cm) { ternServer.showType(cm); }, - "Ctrl-O": function(cm) { ternServer.showDocs(cm); }, - "Alt-.": function(cm) { ternServer.jumpToDef(cm); }, - "Alt-,": function(cm) { ternServer.jumpBack(cm); }, - "Ctrl-Q": function(cm) { ternServer.rename(cm); }, - "Ctrl-.": function(cm) { ternServer.selectName(cm); }, - "'.'": function(cm) { setTimeout(function() { ternServer.complete(cm); }, 100); throw CodeMirror.Pass; } - }) + extraKeys["Ctrl-Space"] = function(cm) { ternServer.complete(cm); }; + extraKeys["Ctrl-I"] = function(cm) { ternServer.showType(cm); }; + extraKeys["Ctrl-O"] = function(cm) { ternServer.showDocs(cm); }; + extraKeys["Alt-."] = function(cm) { ternServer.jumpToDef(cm); }; + extraKeys["Alt-,"] = function(cm) { ternServer.jumpBack(cm); }; + extraKeys["Ctrl-Q"] = function(cm) { ternServer.rename(cm); }; + extraKeys["Ctrl-."] = function(cm) { ternServer.selectName(cm); }; + extraKeys["'.'"] = function(cm) { setTimeout(function() { ternServer.complete(cm); }, 100); throw CodeMirror.Pass; }; editor.on("cursorActivity", function(cm) { ternServer.updateArgHints(cm); }); } else if (mode === "solidity") @@ -119,10 +111,9 @@ setMode = function(mode) { CodeMirror.commands.autocomplete = function(cm) { CodeMirror.showHint(cm, CodeMirror.hint.anyword); } - editor.setOption("extraKeys", { - "Ctrl-Space": "autocomplete" - }) + extraKeys["Ctrl-Space"] = "autocomplete"; } + editor.setOption("extraKeys", extraKeys); }; setClipboardBase64 = function(text) { @@ -133,8 +124,6 @@ var executionMark; highlightExecution = function(start, end) { if (executionMark) executionMark.clear(); - if (start === 0 && end + 1 === editor.getValue().length) - return; // Do not hightlight the whole document. if (debugWarning) debugWarning.clear(); executionMark = editor.markText(editor.posFromIndex(start), editor.posFromIndex(end), { className: "CodeMirror-exechighlight" }); @@ -199,3 +188,5 @@ compilationComplete = function() } compilationCompleteBool = true; } + +editor.setOption("extraKeys", extraKeys); diff --git a/mix/qml/img/broom.png b/mix/qml/img/broom.png deleted file mode 100644 index 76a9a0e0c..000000000 Binary files a/mix/qml/img/broom.png and /dev/null differ diff --git a/mix/qml/img/clearicon.png b/mix/qml/img/clearicon.png new file mode 100644 index 000000000..9760ff27b Binary files /dev/null and b/mix/qml/img/clearicon.png differ diff --git a/mix/qml/img/cleariconactive.png b/mix/qml/img/cleariconactive.png new file mode 100644 index 000000000..dfd829d5d Binary files /dev/null and b/mix/qml/img/cleariconactive.png differ diff --git a/mix/qml/img/copyicon.png b/mix/qml/img/copyicon.png new file mode 100644 index 000000000..836f08b0b Binary files /dev/null and b/mix/qml/img/copyicon.png differ diff --git a/mix/qml/img/copyiconactive.png b/mix/qml/img/copyiconactive.png new file mode 100644 index 000000000..387d7f1fa Binary files /dev/null and b/mix/qml/img/copyiconactive.png differ diff --git a/mix/qml/img/edit_combox.png b/mix/qml/img/edit_combox.png new file mode 100644 index 000000000..be3b35942 Binary files /dev/null and b/mix/qml/img/edit_combox.png differ diff --git a/mix/qml/img/pause_button.png b/mix/qml/img/pause_button.png new file mode 100755 index 000000000..e87889551 Binary files /dev/null and b/mix/qml/img/pause_button.png differ diff --git a/mix/qml/img/pause_button2x.png b/mix/qml/img/pause_button2x.png new file mode 100755 index 000000000..6f45b3236 Binary files /dev/null and b/mix/qml/img/pause_button2x.png differ diff --git a/mix/qml/img/play_button.png b/mix/qml/img/play_button.png new file mode 100755 index 000000000..01a5c85cb Binary files /dev/null and b/mix/qml/img/play_button.png differ diff --git a/mix/qml/img/play_button2x.png b/mix/qml/img/play_button2x.png new file mode 100755 index 000000000..24eaa0659 Binary files /dev/null and b/mix/qml/img/play_button2x.png differ diff --git a/mix/qml/img/searchicon.png b/mix/qml/img/searchicon.png new file mode 100644 index 000000000..c10dc9228 Binary files /dev/null and b/mix/qml/img/searchicon.png differ diff --git a/mix/qml/img/stop_button2x.png b/mix/qml/img/stop_button2x.png new file mode 100644 index 000000000..1727b729a Binary files /dev/null and b/mix/qml/img/stop_button2x.png differ diff --git a/mix/qml/js/ProjectModel.js b/mix/qml/js/ProjectModel.js index 177115f83..25e8dcb77 100644 --- a/mix/qml/js/ProjectModel.js +++ b/mix/qml/js/ProjectModel.js @@ -172,7 +172,7 @@ function getDocumentIndex(documentId) for (var i = 0; i < projectListModel.count; i++) if (projectListModel.get(i).documentId === documentId) return i; - console.error("Cant find document " + documentId); + console.error("Can't find document " + documentId); return -1; } @@ -291,6 +291,14 @@ function getDocument(documentId) { return projectListModel.get(i); } +function getDocumentIdByName(fileName) +{ + for (var i = 0; i < projectListModel.count; i++) + if (projectListModel.get(i).fileName === fileName) + return projectListModel.get(i).documentId; + return null; +} + function removeDocument(documentId) { var i = getDocumentIndex(documentId); var document = projectListModel.get(i); diff --git a/mix/res.qrc b/mix/res.qrc index e509bdcc2..0149b96e4 100644 --- a/mix/res.qrc +++ b/mix/res.qrc @@ -17,7 +17,6 @@ qml/fonts/SourceSerifPro-Semibold.ttf qml/img/available_updates.png qml/img/b64.png - qml/img/broom.png qml/img/bugiconactive.png qml/img/bugiconinactive.png qml/img/closedtriangleindicator.png @@ -49,9 +48,20 @@ qml/img/projecticon.png qml/img/run.png qml/img/search_filled.png + qml/img/play_button2x.png + qml/img/play_button.png + qml/img/pause_button2x.png + qml/img/pause_button.png res/mix_256x256x32.png stdc/config.sol stdc/namereg.sol stdc/std.sol + qml/img/edit_combox.png + qml/img/clearicon.png + qml/img/cleariconactive.png + qml/img/copyicon.png + qml/img/copyiconactive.png + qml/img/searchicon.png + qml/img/stop_button2x.png diff --git a/mix/style.xml b/mix/style.xml new file mode 100644 index 000000000..75626ac24 --- /dev/null +++ b/mix/style.xml @@ -0,0 +1,19 @@ + + + + + + CodeStyleData + + false + 4 + 2 + false + 4 + + + + DisplayName + Eth + + diff --git a/test/ClientBase.cpp b/test/ClientBase.cpp index 2197ac830..304182cfc 100644 --- a/test/ClientBase.cpp +++ b/test/ClientBase.cpp @@ -34,34 +34,45 @@ BOOST_AUTO_TEST_CASE(blocks) { enumerateClients([](Json::Value const& _json, dev::eth::ClientBase& _client) -> void { - for (string const& name: _json["postState"].getMemberNames()) + auto compareState = [&_client](Json::Value const& _o, string const& _name, BlockNumber _blockNumber) -> void { - Json::Value o = _json["postState"][name]; - Address address(name); + Address address(_name); // balanceAt - u256 expectedBalance = u256(o["balance"].asString()); - u256 balance = _client.balanceAt(address); + u256 expectedBalance = u256(_o["balance"].asString()); + u256 balance = _client.balanceAt(address, _blockNumber); ETH_CHECK_EQUAL(expectedBalance, balance); // countAt - u256 expectedCount = u256(o["nonce"].asString()); - u256 count = _client.countAt(address); + u256 expectedCount = u256(_o["nonce"].asString()); + u256 count = _client.countAt(address, _blockNumber); ETH_CHECK_EQUAL(expectedCount, count); // stateAt - for (string const& pos: o["storage"].getMemberNames()) + for (string const& pos: _o["storage"].getMemberNames()) { - u256 expectedState = u256(o["storage"][pos].asString()); - u256 state = _client.stateAt(address, u256(pos)); + u256 expectedState = u256(_o["storage"][pos].asString()); + u256 state = _client.stateAt(address, u256(pos), _blockNumber); ETH_CHECK_EQUAL(expectedState, state); } // codeAt - bytes expectedCode = fromHex(o["code"].asString()); - bytes code = _client.codeAt(address); + bytes expectedCode = fromHex(_o["code"].asString()); + bytes code = _client.codeAt(address, _blockNumber); ETH_CHECK_EQUAL_COLLECTIONS(expectedCode.begin(), expectedCode.end(), - code.begin(), code.end()); + code.begin(), code.end()); + }; + + for (string const& name: _json["postState"].getMemberNames()) + { + Json::Value o = _json["postState"][name]; + compareState(o, name, PendingBlock); + } + + for (string const& name: _json["pre"].getMemberNames()) + { + Json::Value o = _json["pre"][name]; + compareState(o, name, 0); } // number diff --git a/test/SolidityNameAndTypeResolution.cpp b/test/SolidityNameAndTypeResolution.cpp index ddcf36140..531f3bc13 100644 --- a/test/SolidityNameAndTypeResolution.cpp +++ b/test/SolidityNameAndTypeResolution.cpp @@ -460,7 +460,7 @@ BOOST_AUTO_TEST_CASE(function_external_types) uint a; } contract Test { - function boo(uint arg2, bool arg3, bytes8 arg4, bool[2] pairs, uint[] dynamic, C carg) external returns (uint ret) { + function boo(uint arg2, bool arg3, bytes8 arg4, bool[2] pairs, uint[] dynamic, C carg, address[] addresses) external returns (uint ret) { ret = 5; } })"; @@ -471,7 +471,7 @@ BOOST_AUTO_TEST_CASE(function_external_types) auto functions = contract->getDefinedFunctions(); if (functions.empty()) continue; - BOOST_CHECK_EQUAL("boo(uint256,bool,bytes8,bool[2],uint256[],address)", functions[0]->externalSignature()); + BOOST_CHECK_EQUAL("boo(uint256,bool,bytes8,bool[2],uint256[],address,address[])", functions[0]->externalSignature()); } } @@ -503,6 +503,16 @@ BOOST_AUTO_TEST_CASE(function_external_call_not_allowed_conversion) BOOST_CHECK_THROW(parseTextAndResolveNames(text), TypeError); } +// todo delete when implemented +BOOST_AUTO_TEST_CASE(arrays_in_internal_functions) +{ + char const* text = R"( + contract Test { + function foo(address[] addresses) {} + })"; + BOOST_CHECK_THROW(parseTextAndResolveNames(text), TypeError); +} + BOOST_AUTO_TEST_CASE(function_internal_allowed_conversion) { char const* text = R"( diff --git a/test/SolidityOptimizer.cpp b/test/SolidityOptimizer.cpp index 2d5cff7ac..e69d5120e 100644 --- a/test/SolidityOptimizer.cpp +++ b/test/SolidityOptimizer.cpp @@ -303,6 +303,271 @@ BOOST_AUTO_TEST_CASE(cse_associativity2) checkCSE(input, {Instruction::DUP2, Instruction::DUP2, Instruction::ADD, u256(5), Instruction::ADD}); } +BOOST_AUTO_TEST_CASE(cse_storage) +{ + AssemblyItems input{ + u256(0), + Instruction::SLOAD, + u256(0), + Instruction::SLOAD, + Instruction::ADD, + u256(0), + Instruction::SSTORE + }; + checkCSE(input, { + u256(0), + Instruction::DUP1, + Instruction::SLOAD, + Instruction::DUP1, + Instruction::ADD, + Instruction::SWAP1, + Instruction::SSTORE + }); +} + +BOOST_AUTO_TEST_CASE(cse_noninterleaved_storage) +{ + // two stores to the same location should be replaced by only one store, even if we + // read in the meantime + AssemblyItems input{ + u256(7), + Instruction::DUP2, + Instruction::SSTORE, + Instruction::DUP1, + Instruction::SLOAD, + u256(8), + Instruction::DUP3, + Instruction::SSTORE + }; + checkCSE(input, { + u256(8), + Instruction::DUP2, + Instruction::SSTORE, + u256(7) + }); +} + +BOOST_AUTO_TEST_CASE(cse_interleaved_storage) +{ + // stores and reads to/from two unknown locations, should not optimize away the first store + AssemblyItems input{ + u256(7), + Instruction::DUP2, + Instruction::SSTORE, // store to "DUP1" + Instruction::DUP2, + Instruction::SLOAD, // read from "DUP2", might be equal to "DUP1" + u256(0), + Instruction::DUP3, + Instruction::SSTORE // store different value to "DUP1" + }; + checkCSE(input, input); +} + +BOOST_AUTO_TEST_CASE(cse_interleaved_storage_same_value) +{ + // stores and reads to/from two unknown locations, should not optimize away the first store + // but it should optimize away the second, since we already know the value will be the same + AssemblyItems input{ + u256(7), + Instruction::DUP2, + Instruction::SSTORE, // store to "DUP1" + Instruction::DUP2, + Instruction::SLOAD, // read from "DUP2", might be equal to "DUP1" + u256(6), + u256(1), + Instruction::ADD, + Instruction::DUP3, + Instruction::SSTORE // store same value to "DUP1" + }; + checkCSE(input, { + u256(7), + Instruction::DUP2, + Instruction::SSTORE, + Instruction::DUP2, + Instruction::SLOAD + }); +} + +BOOST_AUTO_TEST_CASE(cse_interleaved_storage_at_known_location) +{ + // stores and reads to/from two known locations, should optimize away the first store, + // because we know that the location is different + AssemblyItems input{ + u256(0x70), + u256(1), + Instruction::SSTORE, // store to 1 + u256(2), + Instruction::SLOAD, // read from 2, is different from 1 + u256(0x90), + u256(1), + Instruction::SSTORE // store different value at 1 + }; + checkCSE(input, { + u256(2), + Instruction::SLOAD, + u256(0x90), + u256(1), + Instruction::SSTORE + }); +} + +BOOST_AUTO_TEST_CASE(cse_interleaved_storage_at_known_location_offset) +{ + // stores and reads to/from two locations which are known to be different, + // should optimize away the first store, because we know that the location is different + AssemblyItems input{ + u256(0x70), + Instruction::DUP2, + u256(1), + Instruction::ADD, + Instruction::SSTORE, // store to "DUP1"+1 + Instruction::DUP1, + u256(2), + Instruction::ADD, + Instruction::SLOAD, // read from "DUP1"+2, is different from "DUP1"+1 + u256(0x90), + Instruction::DUP3, + u256(1), + Instruction::ADD, + Instruction::SSTORE // store different value at "DUP1"+1 + }; + checkCSE(input, { + u256(2), + Instruction::DUP2, + Instruction::ADD, + Instruction::SLOAD, + u256(0x90), + u256(1), + Instruction::DUP4, + Instruction::ADD, + Instruction::SSTORE + }); +} + +BOOST_AUTO_TEST_CASE(cse_interleaved_memory_at_known_location_offset) +{ + // stores and reads to/from two locations which are known to be different, + // should not optimize away the first store, because the location overlaps with the load, + // but it should optimize away the second, because we know that the location is different by 32 + AssemblyItems input{ + u256(0x50), + Instruction::DUP2, + u256(2), + Instruction::ADD, + Instruction::MSTORE, // ["DUP1"+2] = 0x50 + u256(0x60), + Instruction::DUP2, + u256(32), + Instruction::ADD, + Instruction::MSTORE, // ["DUP1"+32] = 0x60 + Instruction::DUP1, + Instruction::MLOAD, // read from "DUP1" + u256(0x70), + Instruction::DUP3, + u256(32), + Instruction::ADD, + Instruction::MSTORE, // ["DUP1"+32] = 0x70 + u256(0x80), + Instruction::DUP3, + u256(2), + Instruction::ADD, + Instruction::MSTORE, // ["DUP1"+2] = 0x80 + }; + // If the actual code changes too much, we could also simply check that the output contains + // exactly 3 MSTORE and exactly 1 MLOAD instruction. + checkCSE(input, { + u256(0x50), + u256(2), + Instruction::DUP3, + Instruction::ADD, + Instruction::SWAP1, + Instruction::DUP2, + Instruction::MSTORE, // ["DUP1"+2] = 0x50 + Instruction::DUP2, + Instruction::MLOAD, // read from "DUP1" + u256(0x70), + u256(32), + Instruction::DUP5, + Instruction::ADD, + Instruction::MSTORE, // ["DUP1"+32] = 0x70 + u256(0x80), + Instruction::SWAP1, + Instruction::SWAP2, + Instruction::MSTORE // ["DUP1"+2] = 0x80 + }); +} + +BOOST_AUTO_TEST_CASE(cse_deep_stack) +{ + AssemblyItems input{ + Instruction::ADD, + Instruction::SWAP1, + Instruction::POP, + Instruction::SWAP8, + Instruction::POP, + Instruction::SWAP8, + Instruction::POP, + Instruction::SWAP8, + Instruction::SWAP5, + Instruction::POP, + Instruction::POP, + Instruction::POP, + Instruction::POP, + Instruction::POP, + }; + checkCSE(input, { + Instruction::SWAP4, + Instruction::SWAP12, + Instruction::SWAP3, + Instruction::SWAP11, + Instruction::POP, + Instruction::SWAP1, + Instruction::SWAP3, + Instruction::ADD, + Instruction::SWAP8, + Instruction::POP, + Instruction::SWAP6, + Instruction::POP, + Instruction::POP, + Instruction::POP, + Instruction::POP, + Instruction::POP, + Instruction::POP, + }); +} + +BOOST_AUTO_TEST_CASE(cse_jumpi_no_jump) +{ + AssemblyItems input{ + u256(0), + u256(1), + Instruction::DUP2, + AssemblyItem(PushTag, 1), + Instruction::JUMPI + }; + checkCSE(input, { + u256(0), + u256(1) + }); +} + +BOOST_AUTO_TEST_CASE(cse_jumpi_jump) +{ + AssemblyItems input{ + u256(1), + u256(1), + Instruction::DUP2, + AssemblyItem(PushTag, 1), + Instruction::JUMPI + }; + checkCSE(input, { + u256(1), + Instruction::DUP1, + AssemblyItem(PushTag, 1), + Instruction::JUMP + }); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/dagger.cpp b/test/dagger.cpp index f7230f705..4dda9c4fc 100644 --- a/test/dagger.cpp +++ b/test/dagger.cpp @@ -63,8 +63,8 @@ BOOST_AUTO_TEST_CASE(basic_test) unsigned cacheSize(o["cache_size"].get_int()); h256 cacheHash(o["cache_hash"].get_str()); - BOOST_REQUIRE_EQUAL(Ethasher::get()->cache(header).size(), cacheSize); - BOOST_REQUIRE_EQUAL(sha3(Ethasher::get()->cache(header)), cacheHash); + BOOST_REQUIRE_EQUAL(Ethasher::get()->params(header).cache_size, cacheSize); + BOOST_REQUIRE_EQUAL(sha3(bytesConstRef((byte const*)Ethasher::get()->cache(header), cacheSize)), cacheHash); #if TEST_FULL unsigned fullSize(o["full_size"].get_int()); diff --git a/test/peer.cpp b/test/peer.cpp index 48431504f..904de0e9d 100644 --- a/test/peer.cpp +++ b/test/peer.cpp @@ -57,6 +57,15 @@ BOOST_AUTO_TEST_CASE(host) g_logVerbosity = oldLogVerbosity; } +BOOST_AUTO_TEST_CASE(networkConfig) +{ + Host save("Test", NetworkPreferences(false)); + bytes store(save.saveNetwork()); + + Host restore("Test", NetworkPreferences(false), bytesConstRef(&store)); + BOOST_REQUIRE(save.id() == restore.id()); +} + BOOST_AUTO_TEST_CASE(save_nodes) { std::list hosts; diff --git a/test/stMemoryTestFiller.json b/test/stMemoryTestFiller.json index c1754d52f..23f52b657 100644 --- a/test/stMemoryTestFiller.json +++ b/test/stMemoryTestFiller.json @@ -1461,7 +1461,7 @@ } }, - "stackLimitPush32_1024": { + "stackLimitPush32_1023": { "env" : { "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", "currentNumber" : "0", @@ -1495,7 +1495,7 @@ } }, - "stackLimitPush32_1025": { + "stackLimitPush32_1024": { "env" : { "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", "currentNumber" : "0", @@ -1529,7 +1529,41 @@ } }, - "stackLimitPush31_1024": { + "stackLimitPush32_1025": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "42949672960", + "currentDifficulty" : "256", + "currentTimestamp" : "1", + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : "0", + "code" : "(asm 1023 0x00 MSTORE JUMPDEST 0x0102030405060708090a0102030405060708090a0102030405060708090a0102 0x01 0x00 MLOAD SUB 0x00 MSTORE 0x00 MLOAD 0x06 JUMPI STOP )", + "storage": {} + }, + "a94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { + "balance" : "429496729600", + "nonce" : "0", + "code" : "", + "storage": {} + } + }, + "transaction" : { + "nonce" : "0", + "gasPrice" : "1", + "gasLimit" : "100000", + "to" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "value" : "10", + "secretKey" : "45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8", + "data" : "" + } + }, + + "stackLimitPush31_1023": { "env" : { "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", "currentNumber" : "0", @@ -1563,7 +1597,7 @@ } }, - "stackLimitPush31_1025": { + "stackLimitPush31_1024": { "env" : { "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", "currentNumber" : "0", @@ -1597,7 +1631,41 @@ } }, - "stackLimitGas_1024": { + "stackLimitPush31_1025": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "42949672960", + "currentDifficulty" : "256", + "currentTimestamp" : "1", + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : "0", + "code" : "(asm 1023 0x00 MSTORE JUMPDEST 0x0102030405060708090a0102030405060708090a0102030405060708090a01 0x01 0x00 MLOAD SUB 0x00 MSTORE 0x00 MLOAD 0x06 JUMPI STOP )", + "storage": {} + }, + "a94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { + "balance" : "429496729600", + "nonce" : "0", + "code" : "", + "storage": {} + } + }, + "transaction" : { + "nonce" : "0", + "gasPrice" : "1", + "gasLimit" : "100000", + "to" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "value" : "10", + "secretKey" : "45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8", + "data" : "" + } + }, + + "stackLimitGas_1023": { "env" : { "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", "currentNumber" : "0", @@ -1631,7 +1699,7 @@ } }, - "stackLimitGas_1025": { + "stackLimitGas_1024": { "env" : { "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", "currentNumber" : "0", @@ -1665,6 +1733,40 @@ } }, + "stackLimitGas_1025": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "42949672960", + "currentDifficulty" : "256", + "currentTimestamp" : "1", + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : "0", + "code" : "(asm 1023 0x00 MSTORE JUMPDEST GAS 0x01 0x00 MLOAD SUB 0x00 MSTORE 0x00 MLOAD 0x06 JUMPI STOP )", + "storage": {} + }, + "a94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { + "balance" : "429496729600", + "nonce" : "0", + "code" : "", + "storage": {} + } + }, + "transaction" : { + "nonce" : "0", + "gasPrice" : "1", + "gasLimit" : "100000", + "to" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "value" : "10", + "secretKey" : "45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8", + "data" : "" + } + }, + "mstroe8_dejavu": { "env" : { "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6",