diff --git a/eth/main.cpp b/eth/main.cpp index 9ec6dec98..761f7a9fd 100644 --- a/eth/main.cpp +++ b/eth/main.cpp @@ -44,6 +44,7 @@ #include #include #endif +#include #include "BuildInfo.h" using namespace std; using namespace dev; @@ -112,6 +113,7 @@ void help() << " -c,--client-name Add a name to your client's version string (default: blank)." << endl << " -d,--db-path Load database from path (default: ~/.ethereum " << endl << " /Etherum or Library/Application Support/Ethereum)." << endl + << " -D,--initdag Initialize DAG for mining and exit." << endl << " -e,--ether-price Set the ether price in the reference unit e.g. ¢ (Default: 30.679)." << endl << " -f,--force-mining Mine even when there are no transaction to mine (Default: off)" << endl << " -h,--help Show this help message and exit." << endl @@ -200,6 +202,7 @@ enum class NodeMode int main(int argc, char** argv) { + bool initDAG = false; string listenIP; unsigned short listenPort = 30303; string publicIP; @@ -304,6 +307,8 @@ int main(int argc, char** argv) structuredLogging = true; else if ((arg == "-d" || arg == "--path" || arg == "--db-path") && i + 1 < argc) dbPath = argv[++i]; + else if (arg == "-D" || arg == "--initdag") + initDAG = true; else if ((arg == "-B" || arg == "--block-fees") && i + 1 < argc) { try @@ -436,6 +441,14 @@ int main(int argc, char** argv) &nodesState, miners ); + + if (initDAG) + { + cout << "Initializing DAG. (This will take awhile)" << endl; + Ethasher::get()->full(web3.ethereum()->blockChain().info()); + return 0; + } + web3.setIdealPeerCount(peers); std::shared_ptr gasPricer = make_shared(u256(double(ether / 1000) / etherPrice), u256(blockFees * 1000)); eth::Client* c = mode == NodeMode::Full ? web3.ethereum() : nullptr; diff --git a/libethcore/Params.cpp b/libethcore/Params.cpp index d70443fd9..029f8b47a 100644 --- a/libethcore/Params.cpp +++ b/libethcore/Params.cpp @@ -46,7 +46,6 @@ u256 const c_sha3WordGas = 6; u256 const c_sloadGas = 50; u256 const c_sstoreSetGas = 20000; u256 const c_sstoreResetGas = 5000; -u256 const c_sstoreClearGas = 5000; u256 const c_sstoreRefundGas = 15000; u256 const c_jumpdestGas = 1; u256 const c_logGas = 375; diff --git a/libethcore/Params.h b/libethcore/Params.h index 62cf6b2d8..46b30e2c3 100644 --- a/libethcore/Params.h +++ b/libethcore/Params.h @@ -48,8 +48,7 @@ extern u256 const c_sha3WordGas; ///< Once per word of the SHA3 operation's da extern u256 const c_copyGas; ///< Multiplied by the number of 32-byte words that are copied (round up) for any *COPY operation and added. extern u256 const c_sloadGas; ///< Once per SLOAD operation. extern u256 const c_sstoreSetGas; ///< Once per SSTORE operation if the zeroness changes from zero. -extern u256 const c_sstoreResetGas; ///< Once per SSTORE operation if the zeroness doesn't change. -extern u256 const c_sstoreClearGas; ///< Once per SSTORE operation if the zeroness changes to zero. +extern u256 const c_sstoreResetGas; ///< Once per SSTORE operation if the zeroness does not change from zero. NOTE: when c_sstoreSetGas does not apply. extern u256 const c_sstoreRefundGas; ///< Refunded gas, once per SSTORE operation if the zeroness changes to zero. extern u256 const c_jumpdestGas; ///< Once per JUMPDEST operation. extern u256 const c_logGas; ///< Per LOG* operation. diff --git a/libevm/VM.cpp b/libevm/VM.cpp index 33c943938..853ac25f6 100644 --- a/libevm/VM.cpp +++ b/libevm/VM.cpp @@ -108,7 +108,7 @@ bytesConstRef VM::go(ExtVMFace& _ext, OnOpFunc const& _onOp, uint64_t _steps) runGas = c_sstoreSetGas; else if (_ext.store(m_stack.back()) && !m_stack[m_stack.size() - 2]) { - runGas = c_sstoreClearGas; + runGas = c_sstoreResetGas; _ext.sub.refunds += c_sstoreRefundGas; } else diff --git a/libevmcore/CMakeLists.txt b/libevmcore/CMakeLists.txt index b57d52fa3..6a834936b 100644 --- a/libevmcore/CMakeLists.txt +++ b/libevmcore/CMakeLists.txt @@ -25,6 +25,7 @@ else() endif() target_link_libraries(${EXECUTABLE} devcore) +target_link_libraries(${EXECUTABLE} devcrypto) install( TARGETS ${EXECUTABLE} RUNTIME DESTINATION bin ARCHIVE DESTINATION lib LIBRARY DESTINATION lib ) install( FILES ${HEADERS} DESTINATION include/${EXECUTABLE} ) diff --git a/libevmcore/CommonSubexpressionEliminator.cpp b/libevmcore/CommonSubexpressionEliminator.cpp index 5c6ba95af..d857158b9 100644 --- a/libevmcore/CommonSubexpressionEliminator.cpp +++ b/libevmcore/CommonSubexpressionEliminator.cpp @@ -23,6 +23,7 @@ #include #include +#include #include #include @@ -34,8 +35,8 @@ vector CommonSubexpressionEliminator::getOptimizedItems() { optimizeBreakingItem(); - map initialStackContents; - map targetStackContents; + map initialStackContents; + map targetStackContents; int minHeight = m_stackHeight + 1; if (!m_stackElements.empty()) minHeight = min(minHeight, m_stackElements.begin()->first); @@ -58,18 +59,18 @@ vector CommonSubexpressionEliminator::getOptimizedItems() ostream& CommonSubexpressionEliminator::stream( ostream& _out, - map _initialStack, - map _targetStack + map _initialStack, + map _targetStack ) const { - auto streamExpressionClass = [this](ostream& _out, ExpressionClasses::Id _id) + auto streamExpressionClass = [this](ostream& _out, Id _id) { auto const& expr = m_expressionClasses.representative(_id); _out << " " << dec << _id << ": " << *expr.item; if (expr.sequenceNumber) _out << "@" << dec << expr.sequenceNumber; _out << "("; - for (ExpressionClasses::Id arg: expr.arguments) + for (Id arg: expr.arguments) _out << dec << arg << ","; _out << ")" << endl; }; @@ -77,7 +78,7 @@ ostream& CommonSubexpressionEliminator::stream( _out << "Optimizer analysis:" << endl; _out << "Final stack height: " << dec << m_stackHeight << endl; _out << "Equivalence classes: " << endl; - for (ExpressionClasses::Id eqClass = 0; eqClass < m_expressionClasses.size(); ++eqClass) + for (Id eqClass = 0; eqClass < m_expressionClasses.size(); ++eqClass) streamExpressionClass(_out, eqClass); _out << "Initial stack: " << endl; @@ -119,7 +120,7 @@ void CommonSubexpressionEliminator::feedItem(AssemblyItem const& _item, bool _co ); else if (instruction != Instruction::POP) { - vector arguments(info.args); + vector arguments(info.args); for (int i = 0; i < info.args; ++i) arguments[i] = stackElement(m_stackHeight - i); if (_item.instruction() == Instruction::SSTORE) @@ -130,6 +131,8 @@ void CommonSubexpressionEliminator::feedItem(AssemblyItem const& _item, bool _co storeInMemory(arguments[0], arguments[1]); else if (_item.instruction() == Instruction::MLOAD) setStackElement(m_stackHeight + _item.deposit(), loadFromMemory(arguments[0])); + else if (_item.instruction() == Instruction::SHA3) + setStackElement(m_stackHeight + _item.deposit(), applySha3(arguments.at(0), arguments.at(1))); else setStackElement(m_stackHeight + _item.deposit(), m_expressionClasses.find(_item, arguments, _copyItem)); } @@ -142,7 +145,6 @@ 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); @@ -163,7 +165,7 @@ void CommonSubexpressionEliminator::optimizeBreakingItem() } } -void CommonSubexpressionEliminator::setStackElement(int _stackHeight, ExpressionClasses::Id _class) +void CommonSubexpressionEliminator::setStackElement(int _stackHeight, Id _class) { m_stackElements[_stackHeight] = _class; } @@ -194,26 +196,28 @@ ExpressionClasses::Id CommonSubexpressionEliminator::initialStackElement(int _st return m_expressionClasses.find(AssemblyItem(dupInstruction(1 - _stackHeight))); } -void CommonSubexpressionEliminator::storeInStorage(ExpressionClasses::Id _slot, ExpressionClasses::Id _value) +void CommonSubexpressionEliminator::storeInStorage(Id _slot, Id _value) { 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 + // Copy over all values (i.e. retain knowledge about them) where we know that this store + // operation will not destroy the knowledge. Specifically, we copy storage locations we know + // are different from _slot or locations where we know that the stored value is equal to _value. for (auto const& storageItem: m_storageContent) - if (m_expressionClasses.knownToBeDifferent(storageItem.first, _slot)) + if (m_expressionClasses.knownToBeDifferent(storageItem.first, _slot) || storageItem.second == _value) storageContents.insert(storageItem); m_storageContent = move(storageContents); - ExpressionClasses::Id id = m_expressionClasses.find(Instruction::SSTORE, {_slot, _value}, true, m_sequenceNumber); + 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++; } -ExpressionClasses::Id CommonSubexpressionEliminator::loadFromStorage(ExpressionClasses::Id _slot) +ExpressionClasses::Id CommonSubexpressionEliminator::loadFromStorage(Id _slot) { if (m_storageContent.count(_slot)) return m_storageContent.at(_slot); @@ -221,7 +225,7 @@ ExpressionClasses::Id CommonSubexpressionEliminator::loadFromStorage(ExpressionC return m_storageContent[_slot] = m_expressionClasses.find(Instruction::SLOAD, {_slot}, true, m_sequenceNumber); } -void CommonSubexpressionEliminator::storeInMemory(ExpressionClasses::Id _slot, ExpressionClasses::Id _value) +void CommonSubexpressionEliminator::storeInMemory(Id _slot, Id _value) { if (m_memoryContent.count(_slot) && m_memoryContent[_slot] == _value) // do not execute the store if we know that the value is already there @@ -233,14 +237,14 @@ void CommonSubexpressionEliminator::storeInMemory(ExpressionClasses::Id _slot, E 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); + 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++; } -ExpressionClasses::Id CommonSubexpressionEliminator::loadFromMemory(ExpressionClasses::Id _slot) +ExpressionClasses::Id CommonSubexpressionEliminator::loadFromMemory(Id _slot) { if (m_memoryContent.count(_slot)) return m_memoryContent.at(_slot); @@ -248,6 +252,37 @@ ExpressionClasses::Id CommonSubexpressionEliminator::loadFromMemory(ExpressionCl return m_memoryContent[_slot] = m_expressionClasses.find(Instruction::MLOAD, {_slot}, true, m_sequenceNumber); } +CommonSubexpressionEliminator::Id CommonSubexpressionEliminator::applySha3(Id _start, Id _length) +{ + // Special logic if length is a short constant, otherwise we cannot tell. + u256 const* l = m_expressionClasses.knownConstant(_length); + // unknown or too large length + if (!l || *l > 128) + return m_expressionClasses.find(Instruction::SHA3, {_start, _length}, true, m_sequenceNumber); + + vector arguments; + for (u256 i = 0; i < *l; i += 32) + { + Id slot = m_expressionClasses.find(Instruction::ADD, {_start, m_expressionClasses.find(i)}); + arguments.push_back(loadFromMemory(slot)); + } + if (m_knownSha3Hashes.count(arguments)) + return m_knownSha3Hashes.at(arguments); + Id v; + // If all arguments are known constants, compute the sha3 here + if (all_of(arguments.begin(), arguments.end(), [this](Id _a) { return !!m_expressionClasses.knownConstant(_a); })) + { + bytes data; + for (Id a: arguments) + data += toBigEndian(*m_expressionClasses.knownConstant(a)); + data.resize(size_t(*l)); + v = m_expressionClasses.find(u256(sha3(data))); + } + else + v = m_expressionClasses.find(Instruction::SHA3, {_start, _length}, true, m_sequenceNumber); + return m_knownSha3Hashes[arguments] = v; +} + CSECodeGenerator::CSECodeGenerator( ExpressionClasses& _expressionClasses, vector const& _storeOperations @@ -259,8 +294,8 @@ CSECodeGenerator::CSECodeGenerator( } AssemblyItems CSECodeGenerator::generateCode( - map const& _initialStack, - map const& _targetStackContents + map const& _initialStack, + map const& _targetStackContents ) { m_stack = _initialStack; @@ -280,7 +315,7 @@ AssemblyItems CSECodeGenerator::generateCode( } // store all needed sequenced expressions - set> sequencedExpressions; + set> sequencedExpressions; for (auto const& p: m_neededBy) for (auto id: {p.first, p.second}) if (unsigned seqNr = m_expressionClasses.representative(id).sequenceNumber) @@ -327,19 +362,20 @@ AssemblyItems CSECodeGenerator::generateCode( return m_generatedItems; } -void CSECodeGenerator::addDependencies(ExpressionClasses::Id _c) +void CSECodeGenerator::addDependencies(Id _c) { if (m_neededBy.count(_c)) return; // we already computed the dependencies for _c ExpressionClasses::Expression expr = m_expressionClasses.representative(_c); - for (ExpressionClasses::Id argument: expr.arguments) + for (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 + expr.item->instruction() == Instruction::MLOAD || + expr.item->instruction() == Instruction::SHA3 )) { // this loads an unknown value from storage or memory and thus, in addition to its @@ -347,22 +383,52 @@ void CSECodeGenerator::addDependencies(ExpressionClasses::Id _c) // 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); + Id slotToLoadFrom = expr.arguments.at(0); for (auto const& p: m_storeOperations) { if (p.first.first != target) continue; - ExpressionClasses::Id slot = p.first.second; + 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)) - ) + bool knownToBeIndependent = false; + switch (expr.item->instruction()) + { + case Instruction::SLOAD: + knownToBeIndependent = m_expressionClasses.knownToBeDifferent(slot, slotToLoadFrom); + break; + case Instruction::MLOAD: + knownToBeIndependent = m_expressionClasses.knownToBeDifferentBy32(slot, slotToLoadFrom); + break; + case Instruction::SHA3: + { + Id length = expr.arguments.at(1); + Id offsetToStart = m_expressionClasses.find(Instruction::SUB, {slot, slotToLoadFrom}); + u256 const* o = m_expressionClasses.knownConstant(offsetToStart); + u256 const* l = m_expressionClasses.knownConstant(length); + if (l && *l == 0) + knownToBeIndependent = true; + else if (o) + { + // We could get problems here if both *o and *l are larger than 2**254 + // but it is probably ok for the optimizer to produce wrong code for such cases + // which cannot be executed anyway because of the non-payable price. + if (u2s(*o) <= -32) + knownToBeIndependent = true; + else if (l && u2s(*o) >= 0 && *o >= *l) + knownToBeIndependent = true; + } + break; + } + default: + break; + } + if (knownToBeIndependent) continue; + // note that store and load never have the same sequence number - ExpressionClasses::Id latestStore = storeOps.front().expression; + Id latestStore = storeOps.front().expression; for (auto it = ++storeOps.begin(); it != storeOps.end(); ++it) if (it->sequenceNumber < expr.sequenceNumber) latestStore = it->expression; @@ -372,7 +438,7 @@ void CSECodeGenerator::addDependencies(ExpressionClasses::Id _c) } } -int CSECodeGenerator::generateClassElement(ExpressionClasses::Id _c, bool _allowSequenced) +int CSECodeGenerator::generateClassElement(Id _c, bool _allowSequenced) { // do some cleanup removeStackTopIfPossible(); @@ -392,8 +458,8 @@ int CSECodeGenerator::generateClassElement(ExpressionClasses::Id _c, bool _allow OptimizerException, "Sequence constrained operation requested out of sequence." ); - ExpressionClasses::Ids const& arguments = expr.arguments; - for (ExpressionClasses::Id arg: boost::adaptors::reverse(arguments)) + vector const& arguments = expr.arguments; + for (Id arg: boost::adaptors::reverse(arguments)) generateClassElement(arg); // The arguments are somewhere on the stack now, so it remains to move them at the correct place. @@ -478,7 +544,7 @@ int CSECodeGenerator::generateClassElement(ExpressionClasses::Id _c, bool _allow } } -int CSECodeGenerator::classElementPosition(ExpressionClasses::Id _id) const +int CSECodeGenerator::classElementPosition(Id _id) const { assertThrow( m_classPositions.count(_id) && m_classPositions.at(_id) != c_invalidPosition, @@ -488,7 +554,7 @@ int CSECodeGenerator::classElementPosition(ExpressionClasses::Id _id) const return m_classPositions.at(_id); } -bool CSECodeGenerator::canBeRemoved(ExpressionClasses::Id _element, ExpressionClasses::Id _result) +bool CSECodeGenerator::canBeRemoved(Id _element, Id _result) { // Returns false if _element is finally needed or is needed by a class that has not been // computed yet. Note that m_classPositions also includes classes that were deleted in the meantime. @@ -506,8 +572,8 @@ bool CSECodeGenerator::removeStackTopIfPossible() { if (m_stack.empty()) return false; - assertThrow(m_stack.count(m_stackHeight), OptimizerException, ""); - ExpressionClasses::Id top = m_stack[m_stackHeight]; + assertThrow(m_stack.count(m_stackHeight) > 0, OptimizerException, ""); + Id top = m_stack[m_stackHeight]; if (!canBeRemoved(top)) return false; m_generatedItems.push_back(AssemblyItem(Instruction::POP)); diff --git a/libevmcore/CommonSubexpressionEliminator.h b/libevmcore/CommonSubexpressionEliminator.h index 0dbb47b29..660e9dacc 100644 --- a/libevmcore/CommonSubexpressionEliminator.h +++ b/libevmcore/CommonSubexpressionEliminator.h @@ -57,19 +57,20 @@ using AssemblyItems = std::vector; class CommonSubexpressionEliminator { public: + using Id = ExpressionClasses::Id; struct StoreOperation { enum Target { Memory, Storage }; StoreOperation( Target _target, - ExpressionClasses::Id _slot, + Id _slot, unsigned _sequenceNumber, - ExpressionClasses::Id _expression + Id _expression ): target(_target), slot(_slot), sequenceNumber(_sequenceNumber), expression(_expression) {} Target target; - ExpressionClasses::Id slot; + Id slot; unsigned sequenceNumber; - ExpressionClasses::Id expression; + Id expression; }; /// Feeds AssemblyItems into the eliminator and @returns the iterator pointing at the first @@ -83,8 +84,8 @@ public: /// Streams debugging information to @a _out. std::ostream& stream( std::ostream& _out, - std::map _initialStack = std::map(), - std::map _targetStack = std::map() + std::map _initialStack = std::map(), + std::map _targetStack = std::map() ) const; private: @@ -96,39 +97,42 @@ private: /// Simplifies the given item using /// Assigns a new equivalence class to the next sequence number of the given stack element. - void setStackElement(int _stackHeight, ExpressionClasses::Id _class); + void setStackElement(int _stackHeight, Id _class); /// Swaps the given stack elements in their next sequence number. void swapStackElements(int _stackHeightA, int _stackHeightB); /// Retrieves the current equivalence class fo the given stack element (or generates a new /// one if it does not exist yet). - ExpressionClasses::Id stackElement(int _stackHeight); + Id stackElement(int _stackHeight); /// @returns the equivalence class id of the special initial stack element at the given height /// (must not be positive). - ExpressionClasses::Id initialStackElement(int _stackHeight); + 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); + void storeInStorage(Id _slot, 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); + Id loadFromStorage(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); + void storeInMemory(Id _slot, 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); - + Id loadFromMemory(Id _slot); + /// Finds or creates a new expression that applies the sha3 hash function to the contents in memory. + Id applySha3(Id _start, Id _length); /// Current stack height, can be negative. int m_stackHeight = 0; /// Current stack layout, mapping stack height -> equivalence class - std::map m_stackElements; + 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; + 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; + std::map m_memoryContent; + /// Keeps record of all sha3 hashes that are computed. + std::map, Id> m_knownSha3Hashes; /// Keeps information about which storage or memory slots were written to at which sequence /// number with what instruction. std::vector m_storeOperations; @@ -149,6 +153,7 @@ class CSECodeGenerator public: using StoreOperation = CommonSubexpressionEliminator::StoreOperation; using StoreOperations = std::vector; + using Id = ExpressionClasses::Id; /// Initializes the code generator with the given classes and store operations. /// The store operations have to be sorted by sequence number in ascending order. @@ -159,25 +164,25 @@ public: /// @param _targetStackContents final contents of the stack, by stack height relative to initial /// @note should only be called once on each object. AssemblyItems generateCode( - std::map const& _initialStack, - std::map const& _targetStackContents + std::map const& _initialStack, + std::map const& _targetStackContents ); private: /// Recursively discovers all dependencies to @a m_requests. - void addDependencies(ExpressionClasses::Id _c); + void addDependencies(Id _c); /// Produce code that generates the given element if it is not yet present. /// @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); + int generateClassElement(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; + int classElementPosition(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)); + bool canBeRemoved(Id _element, Id _result = Id(-1)); /// Appends code to remove the topmost stack element if it can be removed. bool removeStackTopIfPossible(); @@ -196,19 +201,19 @@ private: /// Current height of the stack relative to the start. int m_stackHeight = 0; /// If (b, a) is in m_requests then b is needed to compute a. - std::multimap m_neededBy; + std::multimap m_neededBy; /// Current content of the stack. - std::map m_stack; + std::map m_stack; /// Current positions of equivalence classes, equal to c_invalidPosition if already deleted. - std::map m_classPositions; + std::map m_classPositions; /// The actual eqivalence class items and how to compute them. 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; + std::map, StoreOperations> m_storeOperations; /// The set of equivalence classes that should be present on the stack at the end. - std::set m_finalClasses; + std::set m_finalClasses; }; template diff --git a/libevmcore/ExpressionClasses.cpp b/libevmcore/ExpressionClasses.cpp index 1c7a79e6b..81beaac7e 100644 --- a/libevmcore/ExpressionClasses.cpp +++ b/libevmcore/ExpressionClasses.cpp @@ -84,24 +84,35 @@ ExpressionClasses::Id ExpressionClasses::find( 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); + return knownNonZero(find(Instruction::SUB, {_a, _b})); } 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. + u256 const* v = knownConstant(find(Instruction::SUB, {_a, _b})); + // forbidden interval is ["-31", 31] + return v && *v + 31 > u256(62); +} + +bool ExpressionClasses::knownZero(Id _c) +{ + return Pattern(u256(0)).matches(representative(_c), *this); +} + +bool ExpressionClasses::knownNonZero(Id _c) +{ + return Pattern(u256(0)).matches(representative(find(Instruction::ISZERO, {_c})), *this); +} + +u256 const* ExpressionClasses::knownConstant(Id _c) +{ 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); + if (!constant.matches(representative(_c), *this)) + return nullptr; + return &constant.d(); } string ExpressionClasses::fullDAGToString(ExpressionClasses::Id _id) const diff --git a/libevmcore/ExpressionClasses.h b/libevmcore/ExpressionClasses.h index 5179845f6..dba7384ec 100644 --- a/libevmcore/ExpressionClasses.h +++ b/libevmcore/ExpressionClasses.h @@ -78,6 +78,15 @@ public: bool knownToBeDifferent(Id _a, Id _b); /// Similar to @a knownToBeDifferent but require that abs(_a - b) >= 32. bool knownToBeDifferentBy32(Id _a, Id _b); + /// @returns true if the value of the given class is known to be zero. + /// @note that this is not the negation of knownNonZero + bool knownZero(Id _c); + /// @returns true if the value of the given class is known to be nonzero. + /// @note that this is not the negation of knownZero + bool knownNonZero(Id _c); + /// @returns a pointer to the value if the given class is known to be a constant, + /// and a nullptr otherwise. + u256 const* knownConstant(Id _c); std::string fullDAGToString(Id _id) const; @@ -131,7 +140,7 @@ public: /// @returns the id of the matched expression if this pattern is part of a match group. Id id() const { return matchGroupValue().id; } /// @returns the data of the matched expression if this pattern is part of a match group. - u256 d() const { return matchGroupValue().item->data(); } + u256 const& d() const { return matchGroupValue().item->data(); } std::string toString() const; diff --git a/libevmcore/Instruction.cpp b/libevmcore/Instruction.cpp index 23f19ac94..03b6ccf2f 100644 --- a/libevmcore/Instruction.cpp +++ b/libevmcore/Instruction.cpp @@ -21,6 +21,7 @@ #include "Instruction.h" +#include #include #include #include @@ -294,27 +295,42 @@ static const std::map c_instructionInfo = { Instruction::SUICIDE, { "SUICIDE", 0, 1, 0, true, ZeroTier } } }; -string dev::eth::disassemble(bytes const& _mem) +void dev::eth::eachInstruction( + bytes const& _mem, + function const& _onInstruction +) { - stringstream ret; - unsigned numerics = 0; for (auto it = _mem.begin(); it != _mem.end(); ++it) { - byte n = *it; - auto iit = c_instructionInfo.find((Instruction)n); - if (numerics || iit == c_instructionInfo.end() || (byte)iit->first != n) // not an instruction or expecting an argument... + Instruction instr = Instruction(*it); + size_t additional = 0; + if (isValidInstruction(instr)) + additional = instructionInfo(instr).additional; + u256 data; + for (size_t i = 0; i < additional; ++i) { - if (numerics) - numerics--; - ret << "0x" << hex << (int)n << " "; + data <<= 8; + if (it != _mem.end() && ++it != _mem.end()) + data |= *it; } + _onInstruction(instr, data); + } +} + +string dev::eth::disassemble(bytes const& _mem) +{ + stringstream ret; + eachInstruction(_mem, [&](Instruction _instr, u256 const& _data) { + if (!isValidInstruction(_instr)) + ret << "0x" << hex << int(_instr) << " "; else { - auto const& ii = iit->second; - ret << ii.name << " "; - numerics = ii.additional; + InstructionInfo info = instructionInfo(_instr); + ret << info.name << " "; + if (info.additional) + ret << "0x" << hex << _data << " "; } - } + }); return ret.str(); } diff --git a/libevmcore/Instruction.h b/libevmcore/Instruction.h index 07c7b52fd..2ccf5bc34 100644 --- a/libevmcore/Instruction.h +++ b/libevmcore/Instruction.h @@ -21,6 +21,7 @@ #pragma once +#include #include #include #include @@ -253,6 +254,9 @@ bool isValidInstruction(Instruction _inst); /// Convert from string mnemonic to Instruction type. extern const std::map c_instructions; +/// Iterate through EVM code and call a function on each instruction. +void eachInstruction(bytes const& _mem, std::function const& _onInstruction); + /// Convert from EVM code to simple EVM assembly language. std::string disassemble(bytes const& _mem); diff --git a/libevmcore/SemanticInformation.cpp b/libevmcore/SemanticInformation.cpp index e561e7554..139c99ce2 100644 --- a/libevmcore/SemanticInformation.cpp +++ b/libevmcore/SemanticInformation.cpp @@ -52,8 +52,6 @@ bool SemanticInformation::breaksCSEAnalysisBlock(AssemblyItem const& _item) 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; diff --git a/libp2p/Host.cpp b/libp2p/Host.cpp index e49baa1be..5704f4727 100644 --- a/libp2p/Host.cpp +++ b/libp2p/Host.cpp @@ -383,11 +383,12 @@ string Host::pocHost() void Host::addNode(NodeId const& _node, bi::address const& _addr, unsigned short _udpNodePort, unsigned short _tcpPeerPort) { - // TODO: p2p clean this up (bring tested acceptor code over from network branch) - while (isWorking() && !m_run) - this_thread::sleep_for(chrono::milliseconds(50)); - if (!m_run) - return; + // return if network is stopped while waiting on Host::run() or nodeTable to start + while (!haveNetwork()) + if (isWorking()) + this_thread::sleep_for(chrono::milliseconds(50)); + else + return; if (_tcpPeerPort < 30300 || _tcpPeerPort > 30305) cwarn << "Non-standard port being recorded: " << _tcpPeerPort; @@ -636,8 +637,9 @@ void Host::startedWorking() else clog(NetNote) << "p2p.start.notice id:" << id().abridged() << "TCP Listen port is invalid or unavailable."; - m_nodeTable.reset(new NodeTable(m_ioService, m_alias, bi::address::from_string(listenAddress()), listenPort())); - m_nodeTable->setEventHandler(new HostNodeTableHandler(*this)); + shared_ptr nodeTable(new NodeTable(m_ioService, m_alias, bi::address::from_string(listenAddress()), listenPort())); + nodeTable->setEventHandler(new HostNodeTableHandler(*this)); + m_nodeTable = nodeTable; restoreNetwork(&m_restoreNetwork); clog(NetNote) << "p2p.started id:" << id().abridged(); diff --git a/libp2p/Host.h b/libp2p/Host.h index 753be3150..e4d43f233 100644 --- a/libp2p/Host.h +++ b/libp2p/Host.h @@ -141,9 +141,12 @@ public: /// Resets acceptor, socket, and IO service. Called by deallocator. void stop(); - /// @returns if network is running. - bool isStarted() const { return m_run; } + /// @returns if network has been started. + bool isStarted() const { return isWorking(); } + /// @returns if network is started and interactive. + bool haveNetwork() const { return m_run && !!m_nodeTable; } + NodeId id() const { return m_alias.pub(); } /// Validates and starts peer session, taking ownership of _io. Disconnects and returns false upon error. diff --git a/libsolidity/CMakeLists.txt b/libsolidity/CMakeLists.txt index b42b6e5c0..0abd188c1 100644 --- a/libsolidity/CMakeLists.txt +++ b/libsolidity/CMakeLists.txt @@ -27,7 +27,6 @@ endif() target_link_libraries(${EXECUTABLE} ${JSONCPP_LIBRARIES}) target_link_libraries(${EXECUTABLE} evmcore) -target_link_libraries(${EXECUTABLE} devcore) target_link_libraries(${EXECUTABLE} devcrypto) install( TARGETS ${EXECUTABLE} RUNTIME DESTINATION bin ARCHIVE DESTINATION lib LIBRARY DESTINATION lib ) diff --git a/libwebthree/WebThree.cpp b/libwebthree/WebThree.cpp index a74d7fa55..8ea2133f0 100644 --- a/libwebthree/WebThree.cpp +++ b/libwebthree/WebThree.cpp @@ -74,7 +74,7 @@ WebThreeDirect::~WebThreeDirect() void WebThreeDirect::setNetworkPreferences(p2p::NetworkPreferences const& _n, bool _dropPeers) { - auto had = haveNetwork(); + auto had = isNetworkStarted(); if (had) stopNetwork(); m_net.setNetworkPreferences(_n, _dropPeers); diff --git a/libwebthree/WebThree.h b/libwebthree/WebThree.h index 92feb8d40..87cf62d4a 100644 --- a/libwebthree/WebThree.h +++ b/libwebthree/WebThree.h @@ -163,7 +163,7 @@ public: /// Sets the ideal number of peers. void setIdealPeerCount(size_t _n) override; - bool haveNetwork() const override { return m_net.isStarted(); } + bool haveNetwork() const override { return m_net.haveNetwork(); } void setNetworkPreferences(p2p::NetworkPreferences const& _n, bool _dropPeers = false) override; diff --git a/mix/ClientModel.cpp b/mix/ClientModel.cpp index a41b7c083..731a5cb7e 100644 --- a/mix/ClientModel.cpp +++ b/mix/ClientModel.cpp @@ -43,6 +43,7 @@ using namespace dev; using namespace dev::eth; +using namespace std; namespace dev { @@ -54,7 +55,7 @@ class RpcConnector: public jsonrpc::AbstractServerConnector public: virtual bool StartListening() override { return true; } virtual bool StopListening() override { return true; } - virtual bool SendResponse(std::string const& _response, void*) override + virtual bool SendResponse(string const& _response, void*) override { m_response = QString::fromStdString(_response); return true; @@ -102,7 +103,7 @@ QString ClientModel::apiCall(QString const& _message) } catch (...) { - std::cerr << boost::current_exception_diagnostic_information(); + cerr << boost::current_exception_diagnostic_information(); return QString(); } } @@ -126,7 +127,7 @@ void ClientModel::mine() catch (...) { m_mining = false; - std::cerr << boost::current_exception_diagnostic_information(); + cerr << boost::current_exception_diagnostic_information(); emit runFailed(QString::fromStdString(boost::current_exception_diagnostic_information())); } emit miningStateChanged(); @@ -149,7 +150,7 @@ QVariantMap ClientModel::contractAddresses() const { QVariantMap res; for (auto const& c: m_contractAddresses) - res.insert(c.first, QString::fromStdString(dev::toJS(c.second))); + res.insert(c.first, QString::fromStdString(toJS(c.second))); return res; } @@ -158,14 +159,14 @@ void ClientModel::setupState(QVariantMap _state) QVariantList balances = _state.value("accounts").toList(); QVariantList transactions = _state.value("transactions").toList(); - std::map accounts; + map accounts; for (auto const& b: balances) { QVariantMap address = b.toMap(); - accounts.insert(std::make_pair(Secret(address.value("secret").toString().toStdString()), (qvariant_cast(address.value("balance")))->toU256Wei())); + accounts.insert(make_pair(Secret(address.value("secret").toString().toStdString()), (qvariant_cast(address.value("balance")))->toU256Wei())); } - std::vector transactionSequence; + vector transactionSequence; for (auto const& t: transactions) { QVariantMap transaction = t.toMap(); @@ -203,7 +204,7 @@ void ClientModel::setupState(QVariantMap _state) executeSequence(transactionSequence, accounts); } -void ClientModel::executeSequence(std::vector const& _sequence, std::map const& _balances) +void ClientModel::executeSequence(vector const& _sequence, map const& _balances) { if (m_running) BOOST_THROW_EXCEPTION(ExecutionStateException()); @@ -225,7 +226,7 @@ void ClientModel::executeSequence(std::vector const& _seque if (!transaction.stdContractUrl.isEmpty()) { //std contract - dev::bytes const& stdContractCode = m_codeModel->getStdContractCode(transaction.contractId, transaction.stdContractUrl); + bytes const& stdContractCode = m_codeModel->getStdContractCode(transaction.contractId, transaction.stdContractUrl); TransactionSettings stdTransaction = transaction; stdTransaction.gas = 500000;// TODO: get this from std contracts library Address address = deployContract(stdContractCode, stdTransaction); @@ -238,7 +239,7 @@ void ClientModel::executeSequence(std::vector const& _seque CompiledContract const& compilerRes = m_codeModel->contract(transaction.contractId); QFunctionDefinition const* f = nullptr; bytes contractCode = compilerRes.bytes(); - std::shared_ptr contractDef = compilerRes.sharedContract(); + shared_ptr contractDef = compilerRes.sharedContract(); if (transaction.functionId.isEmpty()) f = contractDef->constructor(); else @@ -297,12 +298,12 @@ void ClientModel::executeSequence(std::vector const& _seque } catch(boost::exception const&) { - std::cerr << boost::current_exception_diagnostic_information(); + cerr << boost::current_exception_diagnostic_information(); emit runFailed(QString::fromStdString(boost::current_exception_diagnostic_information())); } - catch(std::exception const& e) + catch(exception const& e) { - std::cerr << boost::current_exception_diagnostic_information(); + cerr << boost::current_exception_diagnostic_information(); emit runFailed(e.what()); } m_running = false; @@ -329,16 +330,16 @@ void ClientModel::showDebuggerForTransaction(ExecutionResult const& _t) { QHash codeMap; codes.push_back(QMachineState::getHumanReadableCode(debugData, code.address, code.code, codeMap)); - codeMaps.push_back(std::move(codeMap)); + codeMaps.push_back(move(codeMap)); //try to resolve contract for source level debugging auto nameIter = m_contractNames.find(code.address); - if (nameIter != m_contractNames.end()) + CompiledContract const* compilerRes = nullptr; + if (nameIter != m_contractNames.end() && (compilerRes = m_codeModel->tryGetContract(nameIter->second))) { - CompiledContract const& compilerRes = m_codeModel->contract(nameIter->second); - eth::AssemblyItems assemblyItems = !_t.isConstructor() ? compilerRes.assemblyItems() : compilerRes.constructorAssemblyItems(); - codes.back()->setDocument(compilerRes.documentId()); - codeItems.push_back(std::move(assemblyItems)); - contracts.push_back(&compilerRes); + eth::AssemblyItems assemblyItems = !_t.isConstructor() ? compilerRes->assemblyItems() : compilerRes->constructorAssemblyItems(); + codes.back()->setDocument(compilerRes->documentId()); + codeItems.push_back(move(assemblyItems)); + contracts.push_back(compilerRes); } else { @@ -353,8 +354,8 @@ void ClientModel::showDebuggerForTransaction(ExecutionResult const& _t) QVariantList states; QVariantList solCallStack; - std::map solLocals; // - std::map storageDeclarations; // + map solLocals; // + map storageDeclarations; // unsigned prevInstructionIndex = 0; for (MachineState const& s: _t.machineStates) @@ -366,7 +367,7 @@ void ClientModel::showDebuggerForTransaction(ExecutionResult const& _t) CompiledContract const* contract = contracts[s.codeIndex]; AssemblyItem const& instruction = codeItems[s.codeIndex][instructionIndex]; - if (instruction.type() == dev::eth::Push && !instruction.data()) + if (instruction.type() == eth::Push && !instruction.data()) { //register new local variable initialization auto localIter = contract->locals().find(LocationPair(instruction.getLocation().start, instruction.getLocation().end)); @@ -374,7 +375,7 @@ void ClientModel::showDebuggerForTransaction(ExecutionResult const& _t) solLocals[s.stack.size()] = new QVariableDeclaration(debugData, localIter.value().name.toStdString(), localIter.value().type); } - if (instruction.type() == dev::eth::Tag) + if (instruction.type() == eth::Tag) { //track calls into functions AssemblyItem const& prevInstruction = codeItems[s.codeIndex][prevInstructionIndex]; @@ -389,7 +390,7 @@ void ClientModel::showDebuggerForTransaction(ExecutionResult const& _t) QVariantMap locals; QVariantList localDeclarations; QVariantMap localValues; - for(auto l: solLocals) + for (auto l: solLocals) if (l.first < (int)s.stack.size()) { if (l.second->type()->name().startsWith("mapping")) @@ -403,42 +404,50 @@ void ClientModel::showDebuggerForTransaction(ExecutionResult const& _t) QVariantMap storage; QVariantList storageDeclarationList; QVariantMap storageValues; - for(auto st: s.storage) - if (st.first < std::numeric_limits::max()) + for (auto st: s.storage) + if (st.first < numeric_limits::max()) { auto storageIter = contract->storage().find(static_cast(st.first)); if (storageIter != contract->storage().end()) { QVariableDeclaration* storageDec = nullptr; - auto decIter = storageDeclarations.find(storageIter.value().name); - if (decIter != storageDeclarations.end()) - storageDec = decIter->second; - else + for (SolidityDeclaration const& codeDec : storageIter.value()) { - storageDec = new QVariableDeclaration(debugData, storageIter.value().name.toStdString(), storageIter.value().type); - storageDeclarations[storageDec->name()] = storageDec; + if (codeDec.type.name.startsWith("mapping")) + continue; //mapping type not yet managed + auto decIter = storageDeclarations.find(codeDec.name); + if (decIter != storageDeclarations.end()) + storageDec = decIter->second; + else + { + storageDec = new QVariableDeclaration(debugData, codeDec.name.toStdString(), codeDec.type); + storageDeclarations[storageDec->name()] = storageDec; + } + storageDeclarationList.push_back(QVariant::fromValue(storageDec)); + storageValues[storageDec->name()] = formatStorageValue(storageDec->type()->type(), s.storage, codeDec.offset, codeDec.slot); } - if (storageDec->type()->name().startsWith("mapping")) - break; //mapping type not yet managed - storageDeclarationList.push_back(QVariant::fromValue(storageDec)); - storageValues[storageDec->name()] = formatValue(storageDec->type()->type(), st.second); } } storage["variables"] = storageDeclarationList; storage["values"] = storageValues; prevInstructionIndex = instructionIndex; - solState = new QSolState(debugData, std::move(storage), std::move(solCallStack), std::move(locals), instruction.getLocation().start, instruction.getLocation().end, QString::fromUtf8(instruction.getLocation().sourceName->c_str())); + + SourceLocation location = instruction.getLocation(); + if (contract->contract()->location() == location || contract->functions().contains(LocationPair(location.start, location.end))) + location = dev::SourceLocation(-1, -1, location.sourceName); + + solState = new QSolState(debugData, move(storage), move(solCallStack), move(locals), location.start, location.end, QString::fromUtf8(location.sourceName->c_str())); } states.append(QVariant::fromValue(new QMachineState(debugData, instructionIndex, s, codes[s.codeIndex], data[s.dataIndex], solState))); } - debugData->setStates(std::move(states)); + debugData->setStates(move(states)); debugDataReady(debugData); } -QVariant ClientModel::formatValue(SolidityType const& _type, dev::u256 const& _value) +QVariant ClientModel::formatValue(SolidityType const& _type, u256 const& _value) { ContractCallDataEncoder decoder; bytes val = toBigEndian(_value); @@ -446,6 +455,45 @@ QVariant ClientModel::formatValue(SolidityType const& _type, dev::u256 const& _v return res; } +QVariant ClientModel::formatStorageValue(SolidityType const& _type, map const& _storage, unsigned _offset, u256 const& _slot) +{ + u256 slot = _slot; + QVariantList values; + ContractCallDataEncoder decoder; + u256 count = 1; + if (_type.dynamicSize) + { + count = _storage.at(slot); + slot = fromBigEndian(sha3(toBigEndian(slot)).asBytes()); + } + else if (_type.array) + count = _type.count; + + unsigned offset = _offset; + while (count--) + { + + auto slotIter = _storage.find(slot); + u256 slotValue = slotIter != _storage.end() ? slotIter->second : u256(); + bytes slotBytes = toBigEndian(slotValue); + auto start = slotBytes.end() - _type.size - offset; + bytes val(32 - _type.size); //prepend with zeroes + val.insert(val.end(), start, start + _type.size); + values.append(decoder.decode(_type, val)); + offset += _type.size; + if ((offset + _type.size) > 32) + { + slot++; + offset = 0; + } + } + + if (!_type.array) + return values[0]; + + return QVariant::fromValue(values); +} + void ClientModel::emptyRecord() { debugDataReady(new QDebugData()); @@ -471,9 +519,9 @@ void ClientModel::callContract(Address const& _contract, bytes const& _data, Tra RecordLogEntry* ClientModel::lastBlock() const { eth::BlockInfo blockInfo = m_client->blockInfo(); - std::stringstream strGas; + stringstream strGas; strGas << blockInfo.gasUsed; - std::stringstream strNumber; + stringstream strNumber; strNumber << blockInfo.number; RecordLogEntry* record = new RecordLogEntry(0, QString::fromStdString(strNumber.str()), tr(" - Block - "), tr("Hash: ") + QString(QString::fromStdString(toHex(blockInfo.hash().ref()))), tr("Gas Used: ") + QString::fromStdString(strGas.str()), QString(), QString(), false, RecordLogEntry::RecordType::Block); QQmlEngine::setObjectOwnership(record, QQmlEngine::JavaScriptOwnership); @@ -496,7 +544,7 @@ void ClientModel::onNewTransaction() unsigned recordIndex = tr.executonIndex; QString transactionIndex = tr.isCall() ? QObject::tr("Call") : QString("%1:%2").arg(block).arg(tr.transactionIndex); QString address = QString::fromStdString(toJS(tr.address)); - QString value = QString::fromStdString(dev::toString(tr.value)); + QString value = QString::fromStdString(toString(tr.value)); QString contract = address; QString function; QString returned; diff --git a/mix/ClientModel.h b/mix/ClientModel.h index 0d7fc2df2..4b597a1ea 100644 --- a/mix/ClientModel.h +++ b/mix/ClientModel.h @@ -204,6 +204,7 @@ private: void onStateReset(); void showDebuggerForTransaction(ExecutionResult const& _t); QVariant formatValue(SolidityType const& _type, dev::u256 const& _value); + QVariant formatStorageValue(SolidityType const& _type, std::map const& _storage, unsigned _offset, dev::u256 const& _slot); std::atomic m_running; std::atomic m_mining; diff --git a/mix/CodeModel.cpp b/mix/CodeModel.cpp index 53ec34e2b..3c4972fcf 100644 --- a/mix/CodeModel.cpp +++ b/mix/CodeModel.cpp @@ -54,8 +54,8 @@ using namespace dev::solidity; class CollectDeclarationsVisitor: public ASTConstVisitor { public: - CollectDeclarationsVisitor(QHash* _functions, QHash* _locals, QHash* _storage): - m_functions(_functions), m_locals(_locals), m_storage(_storage), m_functionScope(false), m_storageSlot(0) {} + CollectDeclarationsVisitor(QHash* _functions, QHash* _locals): + m_functions(_functions), m_locals(_locals), m_functionScope(false) {} private: LocationPair nodeLocation(ASTNode const& _node) { @@ -79,31 +79,30 @@ private: SolidityDeclaration decl; decl.type = CodeModel::nodeType(_node.getType().get()); decl.name = QString::fromStdString(_node.getName()); + decl.slot = 0; + decl.offset = 0; if (m_functionScope) m_locals->insert(nodeLocation(_node), decl); - else - m_storage->insert(m_storageSlot++, decl); return true; } private: QHash* m_functions; QHash* m_locals; - QHash* m_storage; bool m_functionScope; - uint m_storageSlot; }; -dev::eth::AssemblyItems filterLocations(dev::eth::AssemblyItems const& _locations, dev::solidity::ContractDefinition const& _contract, QHash _functions) +QHash collectStorage(dev::solidity::ContractDefinition const& _contract) { - dev::eth::AssemblyItems result; - result.reserve(_locations.size()); - for (dev::eth::AssemblyItem item : _locations) + QHash result; + dev::solidity::ContractType contractType(_contract); + + for (auto v : contractType.getStateVariables()) { - 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); + dev::solidity::VariableDeclaration const* declaration = std::get<0>(v); + dev::u256 slot = std::get<1>(v); + unsigned offset = std::get<2>(v); + result[static_cast(slot)].push_back(SolidityDeclaration { QString::fromStdString(declaration->getName()), CodeModel::nodeType(declaration->getType().get()), slot, offset }); } return result; } @@ -133,10 +132,11 @@ CompiledContract::CompiledContract(const dev::solidity::CompilerStack& _compiler if (contractDefinition.getLocation().sourceName.get()) m_documentId = QString::fromStdString(*contractDefinition.getLocation().sourceName); - CollectDeclarationsVisitor visitor(&m_functions, &m_locals, &m_storage); + CollectDeclarationsVisitor visitor(&m_functions, &m_locals); + m_storage = collectStorage(contractDefinition); contractDefinition.accept(visitor); - m_assemblyItems = filterLocations(_compiler.getRuntimeAssemblyItems(name), contractDefinition, m_functions); - m_constructorAssemblyItems = filterLocations(_compiler.getAssemblyItems(name), contractDefinition, m_functions); + m_assemblyItems = _compiler.getRuntimeAssemblyItems(name); + m_constructorAssemblyItems = _compiler.getAssemblyItems(name); } QString CompiledContract::codeHex() const @@ -220,7 +220,7 @@ QVariantMap CodeModel::contracts() const return result; } -CompiledContract* CodeModel::contractByDocumentId(QString _documentId) const +CompiledContract* CodeModel::contractByDocumentId(QString const& _documentId) const { Guard l(x_contractMap); for (ContractMap::const_iterator c = m_contractMap.cbegin(); c != m_contractMap.cend(); ++c) @@ -229,7 +229,7 @@ CompiledContract* CodeModel::contractByDocumentId(QString _documentId) const return nullptr; } -CompiledContract const& CodeModel::contract(QString _name) const +CompiledContract const& CodeModel::contract(QString const& _name) const { Guard l(x_contractMap); CompiledContract* res = m_contractMap.value(_name); @@ -238,6 +238,13 @@ CompiledContract const& CodeModel::contract(QString _name) const return *res; } +CompiledContract const* CodeModel::tryGetContract(QString const& _name) const +{ + Guard l(x_contractMap); + CompiledContract* res = m_contractMap.value(_name); + return res; +} + void CodeModel::releaseContracts() { for (ContractMap::iterator c = m_contractMap.begin(); c != m_contractMap.end(); ++c) @@ -335,10 +342,9 @@ dev::bytes const& CodeModel::getStdContractCode(const QString& _contractName, co SolidityType CodeModel::nodeType(dev::solidity::Type const* _type) { - SolidityType r { SolidityType::Type::UnsignedInteger, 32, false, false, QString::fromStdString(_type->toString()), std::vector(), std::vector() }; + SolidityType r { SolidityType::Type::UnsignedInteger, 32, 1, false, false, QString::fromStdString(_type->toString()), std::vector(), std::vector() }; if (!_type) return r; - r.dynamicSize = _type->isDynamicallySized(); switch (_type->getCategory()) { case Type::Category::Integer: @@ -367,7 +373,13 @@ SolidityType CodeModel::nodeType(dev::solidity::Type const* _type) if (array->isByteArray()) r.type = SolidityType::Type::Bytes; else - r = nodeType(array->getBaseType().get()); + { + SolidityType elementType = nodeType(array->getBaseType().get()); + elementType.name = r.name; + r = elementType; + } + r.count = static_cast(array->getLength()); + r.dynamicSize = _type->isDynamicallySized(); r.array = true; } break; @@ -384,7 +396,10 @@ SolidityType CodeModel::nodeType(dev::solidity::Type const* _type) r.type = SolidityType::Type::Struct; StructType const* s = dynamic_cast(_type); for(auto const& structMember: s->getMembers()) - r.members.push_back(SolidityDeclaration { QString::fromStdString(structMember.first), nodeType(structMember.second.get()) }); + { + auto slotAndOffset = s->getStorageOffsetsOfMember(structMember.first); + r.members.push_back(SolidityDeclaration { QString::fromStdString(structMember.first), nodeType(structMember.second.get()), slotAndOffset.first, slotAndOffset.second }); + } } break; case Type::Category::Function: diff --git a/mix/CodeModel.h b/mix/CodeModel.h index a4ca31e08..ea3694642 100644 --- a/mix/CodeModel.h +++ b/mix/CodeModel.h @@ -99,7 +99,7 @@ public: QHash const& functions() const { return m_functions; } QHash const& locals() const { return m_locals; } - QHash const& storage() const { return m_storage; } + QHash const& storage() const { return m_storage; } private: uint m_sourceHash; @@ -112,7 +112,7 @@ private: eth::AssemblyItems m_constructorAssemblyItems; QHash m_functions; QHash m_locals; - QHash m_storage; + QHash m_storage; friend class CodeModel; }; @@ -141,10 +141,14 @@ public: /// Get contract code by url. Contract is compiled on first access and cached dev::bytes const& getStdContractCode(QString const& _contractName, QString const& _url); /// Get contract by name - CompiledContract const& contract(QString _name) const; + /// Throws if not found + CompiledContract const& contract(QString const& _name) const; + /// Get contract by name + /// @returns nullptr if not found + CompiledContract const* tryGetContract(QString const& _name) const; /// Find a contract by document id /// @returns CompiledContract object or null if not found - Q_INVOKABLE CompiledContract* contractByDocumentId(QString _documentId) const; + Q_INVOKABLE CompiledContract* contractByDocumentId(QString const& _documentId) const; /// Reset code model Q_INVOKABLE void reset() { reset(QVariantMap()); } /// Convert solidity type info to mix type diff --git a/mix/ContractCallDataEncoder.cpp b/mix/ContractCallDataEncoder.cpp index f991cd36e..56aeb1d34 100644 --- a/mix/ContractCallDataEncoder.cpp +++ b/mix/ContractCallDataEncoder.cpp @@ -48,33 +48,57 @@ void ContractCallDataEncoder::encode(QFunctionDefinition const* _function) void ContractCallDataEncoder::encode(QVariant const& _data, SolidityType const& _type) { + u256 count = 1; + QStringList strList; + if (_type.array) + { + if (_data.type() == QVariant::String) + strList = _data.toString().split(",", QString::SkipEmptyParts); //TODO: proper parsing + else + strList = _data.toStringList(); + count = strList.count(); + + } + else + strList.append(_data.toString()); + if (_type.dynamicSize) { - u256 count = 0; if (_type.type == SolidityType::Type::Bytes) - count = encodeSingleItem(_data, _type, m_dynamicData); + count = encodeSingleItem(_data.toString(), _type, m_dynamicData); else { - QVariantList list = qvariant_cast(_data); - for (auto const& item: list) + count = strList.count(); + for (auto const& item: strList) encodeSingleItem(item, _type, m_dynamicData); - count = list.size(); } bytes sizeEnc(32); toBigEndian(count, sizeEnc); m_encodedData.insert(m_encodedData.end(), sizeEnc.begin(), sizeEnc.end()); } else - encodeSingleItem(_data, _type, m_encodedData); + { + if (_type.array) + count = _type.count; + int c = static_cast(count); + if (strList.size() > c) + strList.erase(strList.begin() + c, strList.end()); + else + while (strList.size() < c) + strList.append(QString()); + + for (auto const& item: strList) + encodeSingleItem(item, _type, m_encodedData); + } } -unsigned ContractCallDataEncoder::encodeSingleItem(QVariant const& _data, SolidityType const& _type, bytes& _dest) +unsigned ContractCallDataEncoder::encodeSingleItem(QString const& _data, SolidityType const& _type, bytes& _dest) { if (_type.type == SolidityType::Type::Struct) BOOST_THROW_EXCEPTION(dev::Exception() << dev::errinfo_comment("Struct parameters are not supported yet")); unsigned const alignSize = 32; - QString src = _data.toString(); + QString src = _data; bytes result; if ((src.startsWith("\"") && src.endsWith("\"")) || (src.startsWith("\'") && src.endsWith("\'"))) @@ -104,9 +128,9 @@ unsigned ContractCallDataEncoder::encodeSingleItem(QVariant const& _data, Solidi } unsigned dataSize = _type.dynamicSize ? result.size() : alignSize; + if (result.size() % alignSize != 0) + result.resize((result.size() & ~(alignSize - 1)) + alignSize); _dest.insert(_dest.end(), result.begin(), result.end()); - if ((_dest.size() - 4) % alignSize != 0) - _dest.resize((_dest.size() & ~(alignSize - 1)) + alignSize); return dataSize; } diff --git a/mix/ContractCallDataEncoder.h b/mix/ContractCallDataEncoder.h index 50c28ea3b..805f26691 100644 --- a/mix/ContractCallDataEncoder.h +++ b/mix/ContractCallDataEncoder.h @@ -58,7 +58,7 @@ public: dev::bytes decodeBytes(dev::bytes const& _rawValue); private: - unsigned encodeSingleItem(QVariant const& _data, SolidityType const& _type, bytes& _dest); + unsigned encodeSingleItem(QString const& _data, SolidityType const& _type, bytes& _dest); bigint decodeInt(dev::bytes const& _rawValue); dev::bytes encodeInt(QString const& _str); QString toString(dev::bigint const& _int); diff --git a/mix/MixApplication.cpp b/mix/MixApplication.cpp index b11b825fb..54c860a8d 100644 --- a/mix/MixApplication.cpp +++ b/mix/MixApplication.cpp @@ -20,6 +20,7 @@ */ #include "MixApplication.h" +#include #include #include #include @@ -101,3 +102,16 @@ void MixApplication::initialize() MixApplication::~MixApplication() { } + +bool MixApplication::notify(QObject * receiver, QEvent * event) +{ + try + { + return QApplication::notify(receiver, event); + } + catch (...) + { + std::cerr << boost::current_exception_diagnostic_information(); + } + return false; +} diff --git a/mix/MixApplication.h b/mix/MixApplication.h index 9756f1b89..c1fd73d66 100644 --- a/mix/MixApplication.h +++ b/mix/MixApplication.h @@ -62,6 +62,7 @@ public: static void initialize(); virtual ~MixApplication(); QQmlApplicationEngine* engine() { return m_engine.get(); } + bool notify(QObject* _receiver, QEvent* _event) override; private: std::unique_ptr m_engine; diff --git a/mix/MixClient.cpp b/mix/MixClient.cpp index cc200e62c..58cab151d 100644 --- a/mix/MixClient.cpp +++ b/mix/MixClient.cpp @@ -185,7 +185,7 @@ void MixClient::executeTransaction(Transaction const& _t, State& _state, bool _c // execute on a state if (!_call) { - _state.execute(lastHashes, _t); + dev::eth::ExecutionResult er =_state.execute(lastHashes, _t); if (_t.isCreation() && _state.code(d.contractAddress).empty()) BOOST_THROW_EXCEPTION(OutOfGas() << errinfo_comment("Not enough gas for contract deployment")); // collect watches diff --git a/mix/QBasicNodeDefinition.cpp b/mix/QBasicNodeDefinition.cpp index 8905caef6..a38fc3c8c 100644 --- a/mix/QBasicNodeDefinition.cpp +++ b/mix/QBasicNodeDefinition.cpp @@ -28,7 +28,7 @@ namespace mix { QBasicNodeDefinition::QBasicNodeDefinition(QObject* _parent, solidity::Declaration const* _d): - QObject(_parent), m_name(QString::fromStdString(_d->getName())) + QObject(_parent), m_name(QString::fromStdString(_d->getName())), m_location(_d->getLocation()) { } diff --git a/mix/QBasicNodeDefinition.h b/mix/QBasicNodeDefinition.h index eb81d4f85..9179905eb 100644 --- a/mix/QBasicNodeDefinition.h +++ b/mix/QBasicNodeDefinition.h @@ -23,6 +23,7 @@ #include #include +#include namespace dev { @@ -47,9 +48,11 @@ public: QBasicNodeDefinition(QObject* _parent, std::string const& _name); /// Get the name of the node. QString name() const { return m_name; } + dev::SourceLocation const& location() { return m_location; } private: QString m_name; + dev::SourceLocation m_location; }; } diff --git a/mix/SolidityType.h b/mix/SolidityType.h index 9990e2e7f..accdb14b4 100644 --- a/mix/SolidityType.h +++ b/mix/SolidityType.h @@ -25,6 +25,7 @@ along with cpp-ethereum. If not, see . #include #include +#include namespace dev { @@ -49,6 +50,7 @@ struct SolidityType }; Type type; unsigned size; //in bytes, + unsigned count; bool array; bool dynamicSize; QString name; @@ -60,7 +62,11 @@ struct SolidityDeclaration { QString name; SolidityType type; + dev::u256 slot; + unsigned offset; }; +using SolidityDeclarations = std::vector; + } } diff --git a/mix/qml.qrc b/mix/qml.qrc index 47d9d3bb9..01074edb3 100644 --- a/mix/qml.qrc +++ b/mix/qml.qrc @@ -61,5 +61,7 @@ qml/js/ProjectModel.js qml/js/QEtherHelper.js qml/js/TransactionHelper.js + qml/js/Printer.js + qml/js/ansi2html.js diff --git a/mix/qml/Application.qml b/mix/qml/Application.qml index b7a065cdf..3cbe847c7 100644 --- a/mix/qml/Application.qml +++ b/mix/qml/Application.qml @@ -178,7 +178,7 @@ ApplicationWindow { id: editStatesAction text: qsTr("Edit States") shortcut: "Ctrl+Alt+E" - onTriggered: stateList.show(); + onTriggered: stateList.open(); } Connections { diff --git a/mix/qml/Debugger.qml b/mix/qml/Debugger.qml index ad03f1456..e5c7e576d 100644 --- a/mix/qml/Debugger.qml +++ b/mix/qml/Debugger.qml @@ -12,6 +12,10 @@ Rectangle { id: debugPanel property alias transactionLog: transactionLog + property alias debugSlider: statesSlider + property alias solLocals: solLocals + property alias solStorage: solStorage + property alias solCallStack: solCallStack signal debugExecuteLocation(string documentId, var location) property string compilationErrorMessage property bool assemblyMode: false diff --git a/mix/qml/DeploymentDialog.qml b/mix/qml/DeploymentDialog.qml index e8af2f664..6ed7afed5 100644 --- a/mix/qml/DeploymentDialog.qml +++ b/mix/qml/DeploymentDialog.qml @@ -2,7 +2,7 @@ import QtQuick 2.2 import QtQuick.Controls 1.1 import QtQuick.Layouts 1.1 import QtQuick.Window 2.0 -import QtQuick.Dialogs 1.1 +import QtQuick.Dialogs 1.2 import QtQuick.Controls.Styles 1.3 import org.ethereum.qml.QEther 1.0 import "js/TransactionHelper.js" as TransactionHelper @@ -11,16 +11,11 @@ import "js/QEtherHelper.js" as QEtherHelper import "." -Window { - +Dialog { id: modalDeploymentDialog modality: Qt.ApplicationModal width: 735 - height: 320 - maximumWidth: width - minimumWidth: width - maximumHeight: height - minimumHeight: height + height: 400 visible: false property alias applicationUrlEth: applicationUrlEth.text property alias applicationUrlHttp: applicationUrlHttp.text @@ -32,8 +27,6 @@ Window { property string currentAccount property alias gasToUse: gasToUseInput.text - color: appStyle.generic.layout.backgroundColor - function close() { visible = false; @@ -41,10 +34,7 @@ Window { function open() { - modalDeploymentDialog.setX((Screen.width - width) / 2); - modalDeploymentDialog.setY((Screen.height - height) / 2); visible = true; - var requests = [{ //accounts jsonrpc: "2.0", @@ -160,136 +150,140 @@ Window { id: lightFont } - Column - { - spacing: 5 + contentItem: Rectangle { + color: appStyle.generic.layout.backgroundColor anchors.fill: parent - anchors.margins: 10 - ColumnLayout + Column { - id: containerDeploy - Layout.fillWidth: true - Layout.preferredHeight: 500 - RowLayout + spacing: 5 + anchors.fill: parent + anchors.margins: 10 + ColumnLayout { - Rectangle + id: containerDeploy + Layout.fillWidth: true + Layout.preferredHeight: 500 + RowLayout { - Layout.preferredWidth: 357 - DefaultLabel + Rectangle { - text: qsTr("Deployment") - font.family: lightFont.name - font.underline: true - anchors.centerIn: parent + Layout.preferredWidth: 357 + DefaultLabel + { + text: qsTr("Deployment") + font.family: lightFont.name + font.underline: true + anchors.centerIn: parent + } } - } - - Button - { - action: displayHelpAction - iconSource: "qrc:/qml/img/help.png" - } - Action { - id: displayHelpAction - tooltip: qsTr("Help") - onTriggered: { - Qt.openUrlExternally("https://github.com/ethereum/wiki/wiki/Mix:-The-DApp-IDE#deployment-to-network") + Button + { + action: displayHelpAction + iconSource: "qrc:/qml/img/help.png" } - } - Button - { - action: openFolderAction - iconSource: "qrc:/qml/img/openedfolder.png" - } - - Action { - id: openFolderAction - enabled: deploymentDialog.packageBase64 !== "" - tooltip: qsTr("Open Package Folder") - onTriggered: { - fileIo.openFileBrowser(projectModel.deploymentDir); + Action { + id: displayHelpAction + tooltip: qsTr("Help") + onTriggered: { + Qt.openUrlExternally("https://github.com/ethereum/wiki/wiki/Mix:-The-DApp-IDE#deployment-to-network") + } } - } - Button - { - action: b64Action - iconSource: "qrc:/qml/img/b64.png" - } + Button + { + action: openFolderAction + iconSource: "qrc:/qml/img/openedfolder.png" + } - Action { - id: b64Action - enabled: deploymentDialog.packageBase64 !== "" - tooltip: qsTr("Copy Base64 conversion to ClipBoard") - onTriggered: { - clipboard.text = deploymentDialog.packageBase64; + Action { + id: openFolderAction + enabled: deploymentDialog.packageBase64 !== "" + tooltip: qsTr("Open Package Folder") + onTriggered: { + fileIo.openFileBrowser(projectModel.deploymentDir); + } } - } - Button - { - action: exitAction - iconSource: "qrc:/qml/img/exit.png" - } + Button + { + action: b64Action + iconSource: "qrc:/qml/img/b64.png" + } - Action { - id: exitAction - tooltip: qsTr("Exit") - onTriggered: { - close() + Action { + id: b64Action + enabled: deploymentDialog.packageBase64 !== "" + tooltip: qsTr("Copy Base64 conversion to ClipBoard") + onTriggered: { + clipboard.text = deploymentDialog.packageBase64; + } } - } - } - GridLayout - { - columns: 2 - width: parent.width + Button + { + action: exitAction + iconSource: "qrc:/qml/img/exit.png" + } - DefaultLabel - { - text: qsTr("Root Registrar address:") + Action { + id: exitAction + tooltip: qsTr("Exit") + onTriggered: { + close() + } + } } - DefaultTextField + GridLayout { - Layout.preferredWidth: 350 - id: registrarAddr - } + columns: 2 + width: parent.width - DefaultLabel - { - text: qsTr("Account used to deploy:") - } + DefaultLabel + { + text: qsTr("Root Registrar address:") + } - Rectangle - { - width: 300 - height: 25 - color: "transparent" - ComboBox { - id: comboAccounts - property var balances: [] - onCurrentIndexChanged : { - if (modelAccounts.count > 0) - { - currentAccount = modelAccounts.get(currentIndex).id; - balance.text = balances[currentIndex]; - } - } - model: ListModel { - id: modelAccounts - } + DefaultTextField + { + Layout.preferredWidth: 350 + id: registrarAddr } DefaultLabel { - anchors.verticalCenter: parent.verticalCenter - anchors.left: comboAccounts.right - anchors.leftMargin: 20 - id: balance; + text: qsTr("Account used to deploy:") + } + + Rectangle + { + width: 300 + height: 25 + color: "transparent" + ComboBox { + id: comboAccounts + property var balances: [] + onCurrentIndexChanged : { + if (modelAccounts.count > 0) + { + currentAccount = modelAccounts.get(currentIndex).id; + balance.text = balances[currentIndex]; + } + } + model: ListModel { + id: modelAccounts + } + } + + DefaultLabel + { + anchors.verticalCenter: parent.verticalCenter + anchors.left: comboAccounts.right + anchors.leftMargin: 20 + id: balance; + } } } @@ -382,120 +376,120 @@ Window { anchors.verticalCenter: parent.verticalCenter } } - } - - Rectangle - { - width: parent.width - height: 1 - color: "#5891d3" - } - ColumnLayout - { - id: containerRegister - Layout.fillWidth: true - Layout.preferredHeight: 500 - RowLayout + Rectangle { - Layout.preferredHeight: 25 - Rectangle - { - Layout.preferredWidth: 356 - DefaultLabel - { - text: qsTr("Registration") - font.family: lightFont.name - font.underline: true - anchors.centerIn: parent - } - } + width: parent.width + height: 1 + color: "#5891d3" } - GridLayout + ColumnLayout { - columns: 2 + id: containerRegister Layout.fillWidth: true - - DefaultLabel + Layout.preferredHeight: 500 + RowLayout { - Layout.preferredWidth: 355 - text: qsTr("Local package URL") + Layout.preferredHeight: 25 + Rectangle + { + Layout.preferredWidth: 356 + DefaultLabel + { + text: qsTr("Registration") + font.family: lightFont.name + font.underline: true + anchors.centerIn: parent + } + } } - DefaultTextField + GridLayout { - Layout.preferredWidth: 350 - id: localPackageUrl - readOnly: true - enabled: rowRegister.isOkToRegister() - } + columns: 2 + Layout.fillWidth: true - DefaultLabel - { - Layout.preferredWidth: 355 - text: qsTr("URL Hint contract address:") - } + DefaultLabel + { + Layout.preferredWidth: 355 + text: qsTr("Local package URL") + } - DefaultTextField - { - Layout.preferredWidth: 350 - id: urlHintAddr - enabled: rowRegister.isOkToRegister() - } + DefaultTextField + { + Layout.preferredWidth: 350 + id: localPackageUrl + readOnly: true + enabled: rowRegister.isOkToRegister() + } - DefaultLabel - { - Layout.preferredWidth: 355 - text: qsTr("Web Application Resources URL: ") - } + DefaultLabel + { + Layout.preferredWidth: 355 + text: qsTr("URL Hint contract address:") + } - DefaultTextField - { - Layout.preferredWidth: 350 - id: applicationUrlHttp - enabled: rowRegister.isOkToRegister() - } - } + DefaultTextField + { + Layout.preferredWidth: 350 + id: urlHintAddr + enabled: rowRegister.isOkToRegister() + } - RowLayout - { - id: rowRegister - Layout.fillWidth: true + DefaultLabel + { + Layout.preferredWidth: 355 + text: qsTr("Web Application Resources URL: ") + } - Rectangle - { - Layout.preferredWidth: 357 - color: "transparent" + DefaultTextField + { + Layout.preferredWidth: 350 + id: applicationUrlHttp + enabled: rowRegister.isOkToRegister() + } } - function isOkToRegister() + RowLayout { - return Object.keys(projectModel.deploymentAddresses).length > 0 && deploymentDialog.packageHash !== ""; - } + id: rowRegister + Layout.fillWidth: true - Button { - action: registerAction - iconSource: "qrc:/qml/img/note.png" - } + Rectangle + { + Layout.preferredWidth: 357 + color: "transparent" + } - Action { - id: registerAction - enabled: rowRegister.isOkToRegister() - tooltip: qsTr("Register hosted Web Application.") - onTriggered: { - if (applicationUrlHttp.text === "" || deploymentDialog.packageHash === "") - { - deployDialog.title = text; - deployDialog.text = qsTr("Please provide the link where the resources are stored and ensure the package is aleary built using the deployment step.") - deployDialog.open(); - return; + function isOkToRegister() + { + return Object.keys(projectModel.deploymentAddresses).length > 0 && deploymentDialog.packageHash !== ""; + } + + Button { + action: registerAction + iconSource: "qrc:/qml/img/note.png" + } + + Action { + id: registerAction + enabled: rowRegister.isOkToRegister() + tooltip: qsTr("Register hosted Web Application.") + onTriggered: { + if (applicationUrlHttp.text === "" || deploymentDialog.packageHash === "") + { + deployDialog.title = text; + deployDialog.text = qsTr("Please provide the link where the resources are stored and ensure the package is aleary built using the deployment step.") + deployDialog.open(); + return; + } + var inError = []; + if (applicationUrlHttp.text.length > 32) + inError.push(qsTr(applicationUrlHttp.text)); + if (!stopForInputError(inError)) + ProjectModelCode.registerToUrlHint(); } - var inError = []; - if (applicationUrlHttp.text.length > 32) - inError.push(qsTr(applicationUrlHttp.text)); - if (!stopForInputError(inError)) - ProjectModelCode.registerToUrlHint(); } } } diff --git a/mix/qml/LogsPane.qml b/mix/qml/LogsPane.qml index d8d73ac0c..21e14e817 100644 --- a/mix/qml/LogsPane.qml +++ b/mix/qml/LogsPane.qml @@ -7,6 +7,11 @@ import org.ethereum.qml.SortFilterProxyModel 1.0 Rectangle { property variant currentStatus; + function clear() + { + logsModel.clear(); + } + function push(_level, _type, _content) { _content = _content.replace(/\n/g, " ") @@ -57,7 +62,7 @@ Rectangle model: SortFilterProxyModel { id: proxyModel source: logsModel - property var roles: ["-", "javascript", "run", "state", "comp"] + property var roles: ["-", "javascript", "run", "state", "comp", "deployment"] Component.onCompleted: { filterType = regEx(proxyModel.roles); @@ -90,7 +95,7 @@ Rectangle return "(?:" + roles.join('|') + ")"; } - filterType: "(?:javascript|run|state|comp)" + filterType: "(?:javascript|run|state|comp|deployment)" filterContent: "" filterSyntax: SortFilterProxyModel.RegExp filterCaseSensitivity: Qt.CaseInsensitive @@ -384,6 +389,50 @@ Rectangle height: parent.height color: logStyle.generic.layout.buttonSeparatorColor2 } + + ToolButton { + id: deloyButton + checkable: true + height: logStyle.generic.layout.headerButtonHeight + width: 50 + anchors.verticalCenter: parent.verticalCenter + checked: true + onCheckedChanged: { + proxyModel.toogleFilter("deployment") + } + tooltip: qsTr("Deployment") + style: + ButtonStyle { + label: + Item { + DefaultLabel { + font.family: logStyle.generic.layout.logLabelFont + font.pointSize: appStyle.absoluteSize(-3) + color: logStyle.generic.layout.logLabelColor + anchors.centerIn: parent + text: qsTr("Deploy.") + } + } + background: + Rectangle { + color: deloyButton.checked ? logStyle.generic.layout.buttonSelected : "transparent" + } + } + } + + Rectangle { + anchors.verticalCenter: parent.verticalCenter + width: 1; + height: parent.height + color: logStyle.generic.layout.buttonSeparatorColor1 + } + + Rectangle { + anchors.verticalCenter: parent.verticalCenter + width: 2; + height: parent.height + color: logStyle.generic.layout.buttonSeparatorColor2 + } } Row diff --git a/mix/qml/NewProjectDialog.qml b/mix/qml/NewProjectDialog.qml index 3ea2a5a4b..3c3c4e649 100644 --- a/mix/qml/NewProjectDialog.qml +++ b/mix/qml/NewProjectDialog.qml @@ -1,10 +1,11 @@ import QtQuick 2.2 import QtQuick.Controls 1.1 import QtQuick.Layouts 1.1 +import QtQuick.Dialogs 1.2 import QtQuick.Window 2.0 import QtQuick.Dialogs 1.1 -Window { +Dialog { id: newProjectWin modality: Qt.ApplicationModal @@ -33,62 +34,65 @@ Window { close(); accepted(); } - - GridLayout { - id: dialogContent - columns: 2 + contentItem: Rectangle { anchors.fill: parent - anchors.margins: 10 - rowSpacing: 10 - columnSpacing: 10 + GridLayout + { + id: dialogContent + columns: 2 + anchors.fill: parent + anchors.margins: 10 + rowSpacing: 10 + columnSpacing: 10 - Label { - text: qsTr("Title") - } - TextField { - id: titleField - focus: true - Layout.fillWidth: true - Keys.onReturnPressed: { - if (okButton.enabled) - acceptAndClose(); + Label { + text: qsTr("Title") } - } - - Label { - text: qsTr("Path") - } - RowLayout { TextField { - id: pathField + id: titleField + focus: true Layout.fillWidth: true Keys.onReturnPressed: { if (okButton.enabled) acceptAndClose(); } } - Button { - text: qsTr("Browse") - onClicked: createProjectFileDialog.open() + + Label { + text: qsTr("Path") + } + RowLayout { + TextField { + id: pathField + Layout.fillWidth: true + Keys.onReturnPressed: { + if (okButton.enabled) + acceptAndClose(); + } + } + Button { + text: qsTr("Browse") + onClicked: createProjectFileDialog.open() + } } - } - RowLayout - { - anchors.bottom: parent.bottom - anchors.right: parent.right; + RowLayout + { + anchors.bottom: parent.bottom + anchors.right: parent.right; - Button { - id: okButton; - enabled: titleField.text != "" && pathField.text != "" - text: qsTr("OK"); - onClicked: { - acceptAndClose(); + Button { + id: okButton; + enabled: titleField.text != "" && pathField.text != "" + text: qsTr("OK"); + onClicked: { + acceptAndClose(); + } + } + Button { + text: qsTr("Cancel"); + onClicked: close(); } - } - Button { - text: qsTr("Cancel"); - onClicked: close(); } } } @@ -102,8 +106,8 @@ Window { var u = createProjectFileDialog.fileUrl.toString(); if (u.indexOf("file://") == 0) u = u.substring(7, u.length) - if (Qt.platform.os == "windows" && u.indexOf("/") == 0) - u = u.substring(1, u.length); + if (Qt.platform.os == "windows" && u.indexOf("/") == 0) + u = u.substring(1, u.length); pathField.text = u; } } diff --git a/mix/qml/StateDialog.qml b/mix/qml/StateDialog.qml index 9dc1549c9..0af27f055 100644 --- a/mix/qml/StateDialog.qml +++ b/mix/qml/StateDialog.qml @@ -1,6 +1,6 @@ import QtQuick 2.2 import QtQuick.Controls 1.1 -import QtQuick.Dialogs 1.1 +import QtQuick.Dialogs 1.2 import QtQuick.Layouts 1.1 import QtQuick.Window 2.0 import QtQuick.Controls.Styles 1.3 @@ -9,7 +9,7 @@ import "js/QEtherHelper.js" as QEtherHelper import "js/TransactionHelper.js" as TransactionHelper import "." -Window { +Dialog { id: modalStateDialog modality: Qt.ApplicationModal @@ -17,7 +17,6 @@ Window { height: 480 title: qsTr("Edit State") visible: false - color: stateDialogStyle.generic.backgroundColor property alias stateTitle: titleField.text property alias isDefault: defaultCheckBox.checked @@ -28,6 +27,10 @@ Window { property var stateAccounts: [] signal accepted + StateDialogStyle { + id: stateDialogStyle + } + function open(index, item, setDefault) { stateIndex = index; stateTitle = item.title; @@ -48,9 +51,6 @@ Window { stateAccounts.push(item.accounts[k]); } - modalStateDialog.setX((Screen.width - width) / 2); - modalStateDialog.setY((Screen.height - height) / 2); - visible = true; isDefault = setDefault; titleField.focus = true; @@ -77,338 +77,334 @@ Window { item.accounts = stateAccounts; return item; } - - StateDialogStyle { - id: stateDialogStyle - } - - ColumnLayout { - anchors.fill: parent - anchors.margins: 10 + contentItem: Rectangle { + color: stateDialogStyle.generic.backgroundColor ColumnLayout { - id: dialogContent - anchors.top: parent.top - - RowLayout - { - Layout.fillWidth: true - DefaultLabel { - Layout.preferredWidth: 75 - text: qsTr("Title") - } - DefaultTextField - { - id: titleField - Layout.fillWidth: true - } - } - - CommonSeparator - { - Layout.fillWidth: true - } - - RowLayout - { - Layout.fillWidth: true - - Rectangle - { - Layout.preferredWidth: 75 - DefaultLabel { - id: accountsLabel - Layout.preferredWidth: 75 - text: qsTr("Accounts") + anchors.fill: parent + ColumnLayout { + anchors.fill: parent + anchors.margins: 10 + ColumnLayout { + id: dialogContent + anchors.top: parent.top + RowLayout + { + Layout.fillWidth: true + DefaultLabel { + Layout.preferredWidth: 75 + text: qsTr("Title") + } + DefaultTextField + { + id: titleField + Layout.fillWidth: true + } } - Button + CommonSeparator { - anchors.top: accountsLabel.bottom - anchors.topMargin: 10 - iconSource: "qrc:/qml/img/plus.png" - action: newAccountAction + Layout.fillWidth: true } - Action { - id: newAccountAction - tooltip: qsTr("Add new Account") - onTriggered: - { - var account = stateListModel.newAccount("1000000", QEther.Ether); - stateAccounts.push(account); - accountsModel.append(account); - } - } - } + RowLayout + { + Layout.fillWidth: true - MessageDialog - { - id: alertAlreadyUsed - text: qsTr("This account is in use. You cannot remove it. The first account is used to deploy config contract and cannot be removed.") - icon: StandardIcon.Warning - standardButtons: StandardButton.Ok - } + Rectangle + { + Layout.preferredWidth: 75 + DefaultLabel { + id: accountsLabel + Layout.preferredWidth: 75 + text: qsTr("Accounts") + } - TableView - { - id: accountsView - Layout.fillWidth: true - model: accountsModel - headerVisible: false - TableViewColumn { - role: "name" - title: qsTr("Name") - width: 150 - delegate: Item { - RowLayout + Button { - height: 25 - width: parent.width - Button + anchors.top: accountsLabel.bottom + anchors.topMargin: 10 + iconSource: "qrc:/qml/img/plus.png" + action: newAccountAction + } + + Action { + id: newAccountAction + tooltip: qsTr("Add new Account") + onTriggered: { - iconSource: "qrc:/qml/img/delete_sign.png" - action: deleteAccountAction + var account = stateListModel.newAccount("1000000", QEther.Ether); + stateAccounts.push(account); + accountsModel.append(account); } + } + } - Action { - id: deleteAccountAction - tooltip: qsTr("Delete Account") - onTriggered: + MessageDialog + { + id: alertAlreadyUsed + text: qsTr("This account is in use. You cannot remove it. The first account is used to deploy config contract and cannot be removed.") + icon: StandardIcon.Warning + standardButtons: StandardButton.Ok + } + + TableView + { + id: accountsView + Layout.fillWidth: true + model: accountsModel + headerVisible: false + TableViewColumn { + role: "name" + title: qsTr("Name") + width: 150 + delegate: Item { + RowLayout { - if (transactionsModel.isUsed(stateAccounts[styleData.row].secret)) - alertAlreadyUsed.open(); - else + height: 25 + width: parent.width + Button { - stateAccounts.splice(styleData.row, 1); - accountsModel.remove(styleData.row); + iconSource: "qrc:/qml/img/delete_sign.png" + action: deleteAccountAction + } + + Action { + id: deleteAccountAction + tooltip: qsTr("Delete Account") + onTriggered: + { + if (transactionsModel.isUsed(stateAccounts[styleData.row].secret)) + alertAlreadyUsed.open(); + else + { + stateAccounts.splice(styleData.row, 1); + accountsModel.remove(styleData.row); + } + } + } + + DefaultTextField { + anchors.verticalCenter: parent.verticalCenter + onTextChanged: { + if (styleData.row > -1) + stateAccounts[styleData.row].name = text; + } + text: { + return styleData.value + } } } } + } - DefaultTextField { - anchors.verticalCenter: parent.verticalCenter - onTextChanged: { - if (styleData.row > -1) - stateAccounts[styleData.row].name = text; - } - text: { - return styleData.value + TableViewColumn { + role: "balance" + title: qsTr("Balance") + width: 200 + delegate: Item { + Ether { + id: balanceField + edit: true + displayFormattedValue: false + value: styleData.value } } } + rowDelegate: + Rectangle { + color: styleData.alternate ? "transparent" : "#f0f0f0" + height: 30; + } } } - TableViewColumn { - role: "balance" - title: qsTr("Balance") - width: 200 - delegate: Item { - Ether { - id: balanceField - edit: true - displayFormattedValue: false - value: styleData.value - } + CommonSeparator + { + Layout.fillWidth: true + } + + RowLayout + { + Layout.fillWidth: true + DefaultLabel { + Layout.preferredWidth: 75 + text: qsTr("Default") + } + CheckBox { + id: defaultCheckBox + Layout.fillWidth: true } } - rowDelegate: - Rectangle { - color: styleData.alternate ? "transparent" : "#f0f0f0" - height: 30; + + CommonSeparator + { + Layout.fillWidth: true } } - } - CommonSeparator - { - Layout.fillWidth: true - } + ColumnLayout { + anchors.top: dialogContent.bottom + anchors.topMargin: 5 + spacing: 0 + RowLayout + { + Layout.preferredWidth: 150 + DefaultLabel { + text: qsTr("Transactions: ") + } - RowLayout - { - Layout.fillWidth: true - DefaultLabel { - Layout.preferredWidth: 75 - text: qsTr("Default") - } - CheckBox { - id: defaultCheckBox - Layout.fillWidth: true - } - } + Button + { + iconSource: "qrc:/qml/img/plus.png" + action: newTrAction + width: 10 + height: 10 + anchors.right: parent.right + } - CommonSeparator - { - Layout.fillWidth: true - } - } + Action { + id: newTrAction + tooltip: qsTr("Create a new transaction") + onTriggered: transactionsModel.addTransaction() + } + } - ColumnLayout { - anchors.top: dialogContent.bottom - anchors.topMargin: 5 - spacing: 0 - RowLayout - { - Layout.preferredWidth: 150 - DefaultLabel { - text: qsTr("Transactions: ") + ScrollView + { + Layout.fillHeight: true + Layout.preferredWidth: 300 + Column + { + Layout.fillHeight: true + Repeater + { + id: trRepeater + model: transactionsModel + delegate: transactionRenderDelegate + visible: transactionsModel.count > 0 + height: 20 * transactionsModel.count + } + } + } } - Button + RowLayout { - iconSource: "qrc:/qml/img/plus.png" - action: newTrAction - width: 10 - height: 10 - anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.right: parent.right; + + Button { + text: qsTr("OK"); + onClicked: { + close(); + accepted(); + } + } + Button { + text: qsTr("Cancel"); + onClicked: close(); + } } - Action { - id: newTrAction - tooltip: qsTr("Create a new transaction") - onTriggered: transactionsModel.addTransaction() - } - } + ListModel { + id: accountsModel - ScrollView - { - Layout.fillHeight: true - Layout.preferredWidth: 300 - Column - { - Layout.fillHeight: true - Repeater + function removeAccount(_i) { - id: trRepeater - model: transactionsModel - delegate: transactionRenderDelegate - visible: transactionsModel.count > 0 - height: 20 * transactionsModel.count + accountsModel.remove(_i); + stateAccounts.splice(_i, 1); } } - } - CommonSeparator - { - Layout.fillWidth: true - } - } - - RowLayout - { - anchors.bottom: parent.bottom - anchors.right: parent.right; - - Button { - text: qsTr("OK"); - onClicked: { - acceptAndClose(); - } - } - Button { - text: qsTr("Cancel"); - onClicked: close(); - } - } - } - - ListModel { - id: accountsModel - - function removeAccount(_i) - { - accountsModel.remove(_i); - stateAccounts.splice(_i, 1); - } - } + ListModel { + id: transactionsModel - ListModel { - id: transactionsModel + function editTransaction(index) { + transactionDialog.stateAccounts = stateAccounts; + transactionDialog.open(index, transactionsModel.get(index)); + } - function editTransaction(index) { - transactionDialog.stateAccounts = stateAccounts; - transactionDialog.open(index, transactionsModel.get(index)); - } + function addTransaction() { - function addTransaction() { + // Set next id here to work around Qt bug + // https://bugreports.qt-project.org/browse/QTBUG-41327 + // Second call to signal handler would just edit the item that was just created, no harm done + var item = TransactionHelper.defaultTransaction(); + transactionDialog.stateAccounts = stateAccounts; + transactionDialog.open(transactionsModel.count, item); + } - // Set next id here to work around Qt bug - // https://bugreports.qt-project.org/browse/QTBUG-41327 - // Second call to signal handler would just edit the item that was just created, no harm done - var item = TransactionHelper.defaultTransaction(); - transactionDialog.stateAccounts = stateAccounts; - transactionDialog.open(transactionsModel.count, item); - } + function deleteTransaction(index) { + stateTransactions.splice(index, 1); + transactionsModel.remove(index); + } - function deleteTransaction(index) { - stateTransactions.splice(index, 1); - transactionsModel.remove(index); - } + function isUsed(secret) + { + for (var i in stateTransactions) + { + if (stateTransactions[i].sender === secret) + return true; + } + return false; + } + } - function isUsed(secret) - { - for (var i in stateTransactions) - { - if (stateTransactions[i].sender === secret) - return true; - } - return false; - } - } + Component { + id: transactionRenderDelegate + RowLayout { + DefaultLabel { + Layout.preferredWidth: 150 + text: functionId + } - Component { - id: transactionRenderDelegate - RowLayout { - DefaultLabel { - Layout.preferredWidth: 150 - text: functionId - } + Button + { + id: deleteBtn + iconSource: "qrc:/qml/img/delete_sign.png" + action: deleteAction + width: 10 + height: 10 + Action { + id: deleteAction + tooltip: qsTr("Delete") + onTriggered: transactionsModel.deleteTransaction(index) + } + } - Button - { - id: deleteBtn - iconSource: "qrc:/qml/img/delete_sign.png" - action: deleteAction - width: 10 - height: 10 - Action { - id: deleteAction - tooltip: qsTr("Delete") - onTriggered: transactionsModel.deleteTransaction(index) + Button + { + iconSource: "qrc:/qml/img/edit.png" + action: editAction + visible: stdContract === false + width: 10 + height: 10 + Action { + id: editAction + tooltip: qsTr("Edit") + onTriggered: transactionsModel.editTransaction(index) + } + } + } } - } - Button - { - iconSource: "qrc:/qml/img/edit.png" - action: editAction - visible: stdContract === false - width: 10 - height: 10 - Action { - id: editAction - tooltip: qsTr("Edit") - onTriggered: transactionsModel.editTransaction(index) + TransactionDialog + { + id: transactionDialog + onAccepted: + { + var item = transactionDialog.getItem(); + + if (transactionDialog.transactionIndex < transactionsModel.count) { + transactionsModel.set(transactionDialog.transactionIndex, item); + stateTransactions[transactionDialog.transactionIndex] = item; + } else { + transactionsModel.append(item); + stateTransactions.push(item); + } + } } } } } - - TransactionDialog - { - id: transactionDialog - onAccepted: - { - var item = transactionDialog.getItem(); - - if (transactionDialog.transactionIndex < transactionsModel.count) { - transactionsModel.set(transactionDialog.transactionIndex, item); - stateTransactions[transactionDialog.transactionIndex] = item; - } else { - transactionsModel.append(item); - stateTransactions.push(item); - } - } - } } diff --git a/mix/qml/StateList.qml b/mix/qml/StateList.qml index 2e1bb4a06..39567feac 100644 --- a/mix/qml/StateList.qml +++ b/mix/qml/StateList.qml @@ -1,39 +1,50 @@ import QtQuick 2.2 import QtQuick.Controls 1.1 import QtQuick.Controls.Styles 1.1 -import QtQuick.Dialogs 1.1 +import QtQuick.Dialogs 1.2 import QtQuick.Layouts 1.1 import QtQuick.Window 2.0 import "." -Window { +Dialog { id: stateListContainer modality: Qt.WindowModal - width: 640 height: 480 - visible: false - ColumnLayout - { + contentItem: Rectangle { anchors.fill: parent - TableView { - id: list - Layout.fillHeight: true - Layout.fillWidth: true - model: projectModel.stateListModel - itemDelegate: renderDelegate - headerDelegate: null - TableViewColumn { - role: "title" - title: qsTr("State") - width: list.width + ColumnLayout + { + anchors.fill: parent + TableView { + id: list + Layout.fillHeight: true + Layout.fillWidth: true + model: projectModel.stateListModel + itemDelegate: renderDelegate + headerDelegate: null + frameVisible: false + TableViewColumn { + role: "title" + title: qsTr("State") + width: list.width + } } - } - Button { - anchors.bottom: parent.bottom - action: addStateAction + Row{ + spacing: 5 + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.rightMargin: 10 + Button { + action: addStateAction + } + + Button { + action: closeAction + } + } } } @@ -69,12 +80,21 @@ Window { } } - Action { - id: addStateAction - text: "&Add State" - shortcut: "Ctrl+T" - enabled: codeModel.hasContract && !clientModel.running; - onTriggered: list.model.addState(); + Row + { + Action { + id: addStateAction + text: qsTr("Add State") + shortcut: "Ctrl+T" + enabled: codeModel.hasContract && !clientModel.running; + onTriggered: list.model.addState(); + } + + Action { + id: closeAction + text: qsTr("Close") + onTriggered: stateListContainer.close(); + } } } diff --git a/mix/qml/StatusPane.qml b/mix/qml/StatusPane.qml index a1f3ebe13..72d9ded9b 100644 --- a/mix/qml/StatusPane.qml +++ b/mix/qml/StatusPane.qml @@ -27,7 +27,6 @@ Rectangle { debugImg.state = ""; currentStatus = { "type": "Comp", "date": Qt.formatDateTime(new Date(), "hh:mm:ss"), "content": status.text, "level": "error" } } - debugRunActionIcon.enabled = codeModel.hasContract; } function infoMessage(text, type) @@ -77,7 +76,11 @@ Rectangle { Connections { target:clientModel - onRunStarted: infoMessage(qsTr("Running transactions..."), "Run"); + onRunStarted: + { + logPane.clear() + infoMessage(qsTr("Running transactions..."), "Run"); + } onRunFailed: errorMessage(format(_message), "Run"); onRunComplete: infoMessage(qsTr("Run complete"), "Run"); onNewBlock: infoMessage(qsTr("New block created"), "State"); @@ -281,24 +284,9 @@ Rectangle { anchors.rightMargin: 9 anchors.verticalCenter: parent.verticalCenter id: debugImg - iconSource: "qrc:/qml/img/bugiconinactive.png" - action: debugRunActionIcon - states: [ - State{ - name: "active" - PropertyChanges { target: debugImg; iconSource: "qrc:/qml/img/bugiconactive.png"} - } - ] - } - Action { - id: debugRunActionIcon - onTriggered: { - if (mainContent.rightViewIsVisible()) - mainContent.hideRightView() - else - mainContent.startQuickDebugging(); - } - enabled: false + text: "" + iconSource: "qrc:/qml/img/bugiconactive.png" + action: showHideRightPanelAction } } } diff --git a/mix/qml/StepActionImage.qml b/mix/qml/StepActionImage.qml index e5129e379..262c99def 100644 --- a/mix/qml/StepActionImage.qml +++ b/mix/qml/StepActionImage.qml @@ -6,6 +6,7 @@ import QtQuick.Controls.Styles 1.1 Rectangle { id: buttonActionContainer + color: "transparent" property string disableStateImg property string enabledStateImg property string buttonTooltip diff --git a/mix/qml/StructView.qml b/mix/qml/StructView.qml index 045b2eabc..2a0c5c68a 100644 --- a/mix/qml/StructView.qml +++ b/mix/qml/StructView.qml @@ -79,11 +79,14 @@ Column function getValue() { + var r = ""; if (value && value[modelData.name] !== undefined) - return value[modelData.name]; + r = value[modelData.name]; else if (modelData.type.category === QSolidityType.Struct) - return {}; - return ""; + r = {}; + if (Array.isArray(r)) + r = r.join(", "); + return r; } } } diff --git a/mix/qml/TransactionDialog.qml b/mix/qml/TransactionDialog.qml index 62a799b26..f53e16a06 100644 --- a/mix/qml/TransactionDialog.qml +++ b/mix/qml/TransactionDialog.qml @@ -1,19 +1,19 @@ import QtQuick 2.2 import QtQuick.Controls 1.1 import QtQuick.Layouts 1.1 +import QtQuick.Dialogs 1.2 import QtQuick.Window 2.0 import QtQuick.Controls.Styles 1.3 import org.ethereum.qml.QEther 1.0 import "js/TransactionHelper.js" as TransactionHelper import "." -Window { +Dialog { id: modalTransactionDialog modality: Qt.ApplicationModal width: 520 - height: 500; + height: 500 visible: false - color: transactionDialogStyle.generic.backgroundColor title: qsTr("Edit Transaction") property int transactionIndex property alias gas: gasValueEdit.gasValue; @@ -27,6 +27,10 @@ Window { property alias stateAccounts: senderComboBox.model signal accepted; + StateDialogStyle { + id: transactionDialogStyle + } + function open(index, item) { rowFunction.visible = !useTransactionDefaultValue; rowValue.visible = !useTransactionDefaultValue; @@ -73,8 +77,6 @@ Window { } } initTypeLoader(); - modalTransactionDialog.setX((Screen.width - width) / 2); - modalTransactionDialog.setY((Screen.height - height) / 2); visible = true; valueField.focus = true; @@ -178,214 +180,219 @@ Window { item.parameters = paramValues; return item; } - - StateDialogStyle { - id: transactionDialogStyle - } - - ColumnLayout { - anchors.fill: parent - anchors.margins: 10 - + contentItem: Rectangle { + color: transactionDialogStyle.generic.backgroundColor ColumnLayout { - id: dialogContent - anchors.top: parent.top - spacing: 10 - RowLayout - { - id: rowSender - Layout.fillWidth: true - height: 150 - DefaultLabel { - Layout.preferredWidth: 75 - text: qsTr("Sender") - } - ComboBox { - - function select(secret) + anchors.fill: parent + ColumnLayout { + anchors.fill: parent + anchors.margins: 10 + + ColumnLayout { + id: dialogContent + anchors.top: parent.top + spacing: 10 + RowLayout { - for (var i in model) - if (model[i].secret === secret) + id: rowSender + Layout.fillWidth: true + height: 150 + DefaultLabel { + Layout.preferredWidth: 75 + text: qsTr("Sender") + } + ComboBox { + + function select(secret) { - currentIndex = i; - break; + for (var i in model) + if (model[i].secret === secret) + { + currentIndex = i; + break; + } } - } - id: senderComboBox - Layout.preferredWidth: 350 - currentIndex: 0 - textRole: "name" - editable: false - } - } + id: senderComboBox + Layout.preferredWidth: 350 + currentIndex: 0 + textRole: "name" + editable: false + } + } - RowLayout - { - id: rowContract - Layout.fillWidth: true - height: 150 - DefaultLabel { - Layout.preferredWidth: 75 - text: qsTr("Contract") - } - ComboBox { - id: contractComboBox - function currentValue() { - return (currentIndex >=0 && currentIndex < contractsModel.count) ? contractsModel.get(currentIndex).cid : ""; + RowLayout + { + id: rowContract + Layout.fillWidth: true + height: 150 + DefaultLabel { + Layout.preferredWidth: 75 + text: qsTr("Contract") + } + ComboBox { + id: contractComboBox + function currentValue() { + return (currentIndex >=0 && currentIndex < contractsModel.count) ? contractsModel.get(currentIndex).cid : ""; + } + Layout.preferredWidth: 350 + currentIndex: -1 + textRole: "text" + editable: false + model: ListModel { + id: contractsModel + } + onCurrentIndexChanged: { + loadFunctions(currentValue()); + } + } } - Layout.preferredWidth: 350 - currentIndex: -1 - textRole: "text" - editable: false - model: ListModel { - id: contractsModel + + RowLayout + { + id: rowFunction + Layout.fillWidth: true + height: 150 + DefaultLabel { + Layout.preferredWidth: 75 + text: qsTr("Function") + } + ComboBox { + id: functionComboBox + Layout.preferredWidth: 350 + currentIndex: -1 + textRole: "text" + editable: false + model: ListModel { + id: functionsModel + } + onCurrentIndexChanged: { + loadParameters(); + } + } } - onCurrentIndexChanged: { - loadFunctions(currentValue()); + + CommonSeparator + { + Layout.fillWidth: true } - } - } - RowLayout - { - id: rowFunction - Layout.fillWidth: true - height: 150 - DefaultLabel { - Layout.preferredWidth: 75 - text: qsTr("Function") - } - ComboBox { - id: functionComboBox - Layout.preferredWidth: 350 - currentIndex: -1 - textRole: "text" - editable: false - model: ListModel { - id: functionsModel + RowLayout + { + id: rowValue + Layout.fillWidth: true + height: 150 + DefaultLabel { + Layout.preferredWidth: 75 + text: qsTr("Value") + } + Ether { + id: valueField + edit: true + displayFormattedValue: true + } } - onCurrentIndexChanged: { - loadParameters(); + + CommonSeparator + { + Layout.fillWidth: true } - } - } - CommonSeparator - { - Layout.fillWidth: true - } + RowLayout + { + id: rowGas + Layout.fillWidth: true + height: 150 + DefaultLabel { + Layout.preferredWidth: 75 + text: qsTr("Gas") + } + + DefaultTextField + { + property variant gasValue + onGasValueChanged: text = gasValue.value(); + onTextChanged: gasValue.setValue(text); + implicitWidth: 200 + id: gasValueEdit; + } + } - RowLayout - { - id: rowValue - Layout.fillWidth: true - height: 150 - DefaultLabel { - Layout.preferredWidth: 75 - text: qsTr("Value") - } - Ether { - id: valueField - edit: true - displayFormattedValue: true - } - } + CommonSeparator + { + Layout.fillWidth: true + } - CommonSeparator - { - Layout.fillWidth: true - } + RowLayout + { + id: rowGasPrice + Layout.fillWidth: true + height: 150 + DefaultLabel { + Layout.preferredWidth: 75 + text: qsTr("Gas Price") + } + Ether { + id: gasPriceField + edit: true + displayFormattedValue: true + } + } - RowLayout - { - id: rowGas - Layout.fillWidth: true - height: 150 - DefaultLabel { - Layout.preferredWidth: 75 - text: qsTr("Gas") - } + CommonSeparator + { + Layout.fillWidth: true + } - DefaultTextField - { - property variant gasValue - onGasValueChanged: text = gasValue.value(); - onTextChanged: gasValue.setValue(text); - implicitWidth: 200 - id: gasValueEdit; - } - } + DefaultLabel { + id: paramLabel + text: qsTr("Parameters:") + Layout.preferredWidth: 75 + } - CommonSeparator - { - Layout.fillWidth: true - } + ScrollView + { + id: paramScroll + anchors.top: paramLabel.bottom + anchors.topMargin: 10 + Layout.fillWidth: true + Layout.fillHeight: true + StructView + { + id: typeLoader + Layout.preferredWidth: 150 + members: paramsModel; + } + } - RowLayout - { - id: rowGasPrice - Layout.fillWidth: true - height: 150 - DefaultLabel { - Layout.preferredWidth: 75 - text: qsTr("Gas Price") - } - Ether { - id: gasPriceField - edit: true - displayFormattedValue: true + CommonSeparator + { + Layout.fillWidth: true + visible: paramsModel.length > 0 + } } } - CommonSeparator - { - Layout.fillWidth: true - } - - DefaultLabel { - id: paramLabel - text: qsTr("Parameters:") - Layout.preferredWidth: 75 - } - - ScrollView + RowLayout { - id: paramScroll - anchors.top: paramLabel.bottom - anchors.topMargin: 10 - Layout.fillWidth: true - Layout.fillHeight: true - StructView - { - id: typeLoader - Layout.preferredWidth: 150 - members: paramsModel; + anchors.bottom: parent.bottom + anchors.right: parent.right; + + Button { + text: qsTr("OK"); + onClicked: { + close(); + accepted(); + } } - } - CommonSeparator - { - Layout.fillWidth: true - visible: paramsModel.length > 0 - } - } - - RowLayout - { - anchors.bottom: parent.bottom - anchors.right: parent.right; - - Button { - text: qsTr("OK"); - onClicked: { - acceptAndClose(); + Button { + text: qsTr("Cancel"); + onClicked: close(); + Layout.fillWidth: true } } - Button { - text: qsTr("Cancel"); - onClicked: close(); - } } } } + diff --git a/mix/qml/VariablesView.qml b/mix/qml/VariablesView.qml index 191ec52af..2670a5cb0 100644 --- a/mix/qml/VariablesView.qml +++ b/mix/qml/VariablesView.qml @@ -2,7 +2,6 @@ import QtQuick 2.2 import QtQuick.Controls 1.1 import QtQuick.Controls.Styles 1.1 import QtQuick.Layouts 1.1 -import "." DebugInfoList { diff --git a/mix/qml/WebPreview.qml b/mix/qml/WebPreview.qml index d2b52be65..58bb3c64d 100644 --- a/mix/qml/WebPreview.qml +++ b/mix/qml/WebPreview.qml @@ -13,7 +13,10 @@ Item { property string pendingPageUrl: "" property bool initialized: false property alias urlInput: urlInput + property alias webView: webView + property string webContent; //for testing signal javaScriptMessage(var _level, string _sourceId, var _lineNb, string _content) + signal webContentReady function setPreviewUrl(url) { if (!initialized) @@ -57,6 +60,13 @@ Item { action(i); } + function getContent() { + webView.runJavaScript("getContent()", function(result) { + webContent = result; + webContentReady(); + }); + } + function changePage() { setPreviewUrl(urlInput.text); } @@ -345,7 +355,7 @@ Item { height: 22 width: 22 action: clearAction - iconSource: "qrc:/qml/img/broom.png" + iconSource: "qrc:/qml/img/cleariconactive.png" } Action { @@ -423,6 +433,7 @@ Item { id: resultTextArea width: expressionPanel.width wrapMode: Text.Wrap + textFormat: Text.RichText font.family: webPreviewStyle.general.fontName font.pointSize: appStyle.absoluteSize(-3) backgroundVisible: true diff --git a/mix/qml/html/WebContainer.html b/mix/qml/html/WebContainer.html index dfce64979..30e203bed 100644 --- a/mix/qml/html/WebContainer.html +++ b/mix/qml/html/WebContainer.html @@ -3,6 +3,8 @@ + + diff --git a/mix/qml/js/Printer.js b/mix/qml/js/Printer.js new file mode 100644 index 000000000..2c53fe573 --- /dev/null +++ b/mix/qml/js/Printer.js @@ -0,0 +1,90 @@ +var prettyPrint = (function () { + function pp(object, indent) { + try { + JSON.stringify(object, null, 2); + } catch (e) { + return pp(e, indent); + } + + var str = ""; + if(object instanceof Array) { + str += "["; + for(var i = 0, l = object.length; i < l; i++) { + str += pp(object[i], indent); + if(i < l-1) { + str += ", "; + } + } + str += " ]"; + } else if (object instanceof Error) { + str += "\e[31m" + "Error:\e[0m " + object.message; + } else if (isBigNumber(object)) { + str += "\e[32m'" + object.toString(10) + "'"; + } else if(typeof(object) === "object") { + str += "{\n"; + indent += " "; + var last = getFields(object).pop() + getFields(object).forEach(function (k) { + str += indent + k + ": "; + try { + str += pp(object[k], indent); + } catch (e) { + str += pp(e, indent); + } + if(k !== last) { + str += ","; + } + str += "\n"; + }); + str += indent.substr(2, indent.length) + "}"; + } else if(typeof(object) === "string") { + str += "\e[32m'" + object + "'"; + } else if(typeof(object) === "undefined") { + str += "\e[1m\e[30m" + object; + } else if(typeof(object) === "number") { + str += "\e[31m" + object; + } else if(typeof(object) === "function") { + str += "\e[35m[Function]"; + } else { + str += object; + } + str += "\e[0m"; + return str; + } + var redundantFields = [ + 'valueOf', + 'toString', + 'toLocaleString', + 'hasOwnProperty', + 'isPrototypeOf', + 'propertyIsEnumerable', + 'constructor', + '__defineGetter__', + '__defineSetter__', + '__lookupGetter__', + '__lookupSetter__', + '__proto__' + ]; + var getFields = function (object) { + var result = Object.getOwnPropertyNames(object); + if (object.constructor && object.constructor.prototype) { + result = result.concat(Object.getOwnPropertyNames(object.constructor.prototype)); + } + return result.filter(function (field) { + return redundantFields.indexOf(field) === -1; + }); + }; + var isBigNumber = function (object) { + return typeof BigNumber !== 'undefined' && object instanceof BigNumber; + }; + function prettyPrintI(/* */) { + var args = arguments; + var ret = ""; + for (var i = 0, l = args.length; i < l; i++) { + ret += pp(args[i], "") + "\n"; + } + return ret; + } + return prettyPrintI; +})(); + diff --git a/mix/qml/js/TransactionHelper.js b/mix/qml/js/TransactionHelper.js index 5668030be..566b04910 100644 --- a/mix/qml/js/TransactionHelper.js +++ b/mix/qml/js/TransactionHelper.js @@ -25,7 +25,7 @@ function rpcCall(requests, callBack) if (httpRequest.readyState === XMLHttpRequest.DONE) { if (httpRequest.status !== 200 || httpRequest.responseText === "") { - var errorText = qsTr("Deployment error: Error while registering Dapp ") + httpRequest.status; + var errorText = qsTr("Unable to initiate request to the live network. Please verify your ethereum node is up.") + qsTr(" Error status: ") + httpRequest.status; console.log(errorText); deploymentError(errorText); } diff --git a/mix/qml/js/ansi2html.js b/mix/qml/js/ansi2html.js new file mode 100644 index 000000000..8f53fab24 --- /dev/null +++ b/mix/qml/js/ansi2html.js @@ -0,0 +1,106 @@ +var ansi2html = (function(){ + + function ansi2htmlI(str) { + // this lib do not support \e + str = str.replace(/e\[/g, '['); + // nor line breaks + str = '
' + str.replace(/\n/g, '
') + '
'; + var props = {} + , open = false + + var stylemap = + { bold: "font-weight" + , underline: "text-decoration" + , color: "color" + , background: "background" + } + + function style() { + var key, val, style = [] + for (var key in props) { + val = props[key] + if (!val) continue + if (val == true) { + style.push(stylemap[key] + ':' + key) + } else { + style.push(stylemap[key] + ':' + val) + } + } + return style.join(';') + } + + + function tag(code) { + var i + , tag = '' + , n = ansi2htmlI.table[code] + + if (open) tag += '' + open = false + + if (n) { + for (i in n) props[i] = n[i] + tag += '' + open = true + } else { + props = {} + } + + return tag + } + + return str.replace(/\[(\d+;)?(\d+)*m/g, function(match, b1, b2) { + var i, code, res = '' + if (b2 == '' || b2 == null) b2 = '0' + for (i = 1; i < arguments.length - 2; i++) { + if (!arguments[i]) continue + code = parseInt(arguments[i]) + res += tag(code) + } + return res + }) + tag() + } + + /* not implemented: + * italic + * blink + * invert + * strikethrough + */ + ansi2htmlI.table = + { 0: null + , 1: { bold: true } + , 3: { italic: true } + , 4: { underline: true } + , 5: { blink: true } + , 6: { blink: true } + , 7: { invert: true } + , 9: { strikethrough: true } + , 23: { italic: false } + , 24: { underline: false } + , 25: { blink: false } + , 27: { invert: false } + , 29: { strikethrough: false } + , 30: { color: 'black' } + , 31: { color: 'red' } + , 32: { color: 'green' } + , 33: { color: 'yellow' } + , 34: { color: 'blue' } + , 35: { color: 'magenta' } + , 36: { color: 'cyan' } + , 37: { color: 'white' } + , 39: { color: null } + , 40: { background: 'black' } + , 41: { background: 'red' } + , 42: { background: 'green' } + , 43: { background: 'yellow' } + , 44: { background: 'blue' } + , 45: { background: 'magenta' } + , 46: { background: 'cyan' } + , 47: { background: 'white' } + , 49: { background: null } + } + + return ansi2htmlI; +})(); + diff --git a/mix/test/TestService.cpp b/mix/test/TestService.cpp index 7ad2bc13d..751c2379a 100644 --- a/mix/test/TestService.cpp +++ b/mix/test/TestService.cpp @@ -20,6 +20,7 @@ * Ethereum IDE client. */ +#include #include "TestService.h" #include #include @@ -111,6 +112,12 @@ bool TestService::waitForSignal(QObject* _item, QString _signalName, int _timeou return spy.size(); } +bool TestService::waitForRendering(QObject* _item, int timeout) +{ + QWindow* window = eventWindow(_item); + return waitForSignal(window, "frameSwapped()", timeout); +} + bool TestService::keyPress(QObject* _item, int _key, int _modifiers, int _delay) { QWindow* window = eventWindow(_item); @@ -156,6 +163,7 @@ bool TestService::keyClickChar(QObject* _item, QString const& _character, int _m bool TestService::mouseClick(QObject* _item, qreal _x, qreal _y, int _button, int _modifiers, int _delay) { QWindow* window = qobject_cast(_item); + QMetaObject const* mo = _item->metaObject(); if (!window) window = eventWindow(_item); mouseEvent(MouseClick, window, _item, Qt::MouseButton(_button), Qt::KeyboardModifiers(_modifiers), QPointF(_x, _y), _delay); @@ -170,18 +178,22 @@ void TestService::setTargetWindow(QObject* _window) window->requestActivate(); } + QWindow* TestService::eventWindow(QObject* _item) { QQuickItem* item = qobject_cast(_item); if (item && item->window()) return item->window(); - QQuickWindow* window = qobject_cast(_item); + QWindow* window = qobject_cast(_item); + if (!window && _item->parent()) + window = eventWindow(_item->parent()); if (!window) window = qobject_cast(m_targetWindow); if (window) { window->requestActivate(); + std::cout << window->title().toStdString(); return window; } item = qobject_cast(m_targetWindow); diff --git a/mix/test/TestService.h b/mix/test/TestService.h index fbf6533e6..be65cf558 100644 --- a/mix/test/TestService.h +++ b/mix/test/TestService.h @@ -42,6 +42,7 @@ public: public slots: bool waitForSignal(QObject* _item, QString _signalName, int _timeout); + bool waitForRendering(QObject* _item, int timeout); bool keyPress(QObject* _item, int _key, int _modifiers, int _delay); bool keyRelease(QObject* _item, int _key, int _modifiers, int _delay); bool keyClick(QObject* _item, int _key, int _modifiers, int _delay); diff --git a/mix/test/qml/TestMain.qml b/mix/test/qml/TestMain.qml index 65b5a5076..0da565ac5 100644 --- a/mix/test/qml/TestMain.qml +++ b/mix/test/qml/TestMain.qml @@ -2,6 +2,8 @@ import QtQuick 2.2 import QtTest 1.1 import org.ethereum.qml.TestService 1.0 import "../../qml" +import "js/TestDebugger.js" as TestDebugger +import "js/TestTutorial.js" as TestTutorial TestCase { @@ -14,6 +16,8 @@ TestCase { if (el === undefined) el = mainApplication; + if (el.contentItem) //for dialgos + el = el.contentItem for (var c in str) { @@ -23,6 +27,11 @@ TestCase } } + Application + { + id: mainApplication + } + function newProject() { waitForRendering(mainApplication.mainContent, 10000); @@ -43,100 +52,25 @@ TestCase fail("not compiled"); } - function clickElement(el, x, y) + function editHtml(c) { - ts.mouseClick(el, x, y, Qt.LeftButton, Qt.NoModifier, 10) - } - - function test_defaultTransactionSequence() - { - newProject(); - editContract( - "contract Contract {\r" + - " function Contract() {\r" + - " uint x = 69;\r" + - " uint y = 5;\r" + - " for (uint i = 0; i < y; ++i) {\r" + - " x += 42;\r" + - " z += x;\r" + - " }\r" + - " }\r" + - " uint z;\r" + - "}\r" - ); - if (!ts.waitForSignal(mainApplication.clientModel, "runComplete()", 5000)) - fail("not run"); - tryCompare(mainApplication.mainContent.rightPane.transactionLog.transactionModel, "count", 3); - } - - function test_transactionWithParameter() - { - newProject(); - editContract( - "contract Contract {\r" + - " function setZ(uint256 x) {\r" + - " z = x;\r" + - " }\r" + - " function getZ() returns(uint256) {\r" + - " return z;\r" + - " }\r" + - " uint z;\r" + - "}\r" - ); - mainApplication.projectModel.stateListModel.editState(0); - mainApplication.projectModel.stateDialog.model.addTransaction(); - var transactionDialog = mainApplication.projectModel.stateDialog.transactionDialog; - transactionDialog.selectFunction("setZ"); - clickElement(transactionDialog, 140, 300); - ts.typeString("442", transactionDialog); - transactionDialog.acceptAndClose(); - mainApplication.projectModel.stateDialog.model.addTransaction(); - transactionDialog.selectFunction("getZ"); - transactionDialog.acceptAndClose(); - mainApplication.projectModel.stateDialog.acceptAndClose(); - mainApplication.mainContent.startQuickDebugging(); + mainApplication.projectModel.openDocument("index.html"); wait(1); - if (!ts.waitForSignal(mainApplication.clientModel, "runComplete()", 5000)) - fail("not run"); - tryCompare(mainApplication.mainContent.rightPane.transactionLog.transactionModel, "count", 5); - tryCompare(mainApplication.mainContent.rightPane.transactionLog.transactionModel.get(4), "returned", "(442)"); + mainApplication.mainContent.codeEditor.getEditor("index.html").setText(c); + ts.keyPressChar(mainApplication, "S", Qt.ControlModifier, 200); //Ctrl+S } - function test_constructorParameters() + function clickElement(el, x, y) { - newProject(); - editContract( - "contract Contract {\r" + - " function Contract(uint256 x) {\r" + - " z = x;\r" + - " }\r" + - " function getZ() returns(uint256) {\r" + - " return z;\r" + - " }\r" + - " uint z;\r" + - "}\r" - ); - mainApplication.projectModel.stateListModel.editState(0); - mainApplication.projectModel.stateDialog.model.editTransaction(2); - var transactionDialog = mainApplication.projectModel.stateDialog.transactionDialog; - clickElement(transactionDialog, 140, 300); - ts.typeString("442", transactionDialog); - transactionDialog.acceptAndClose(); - mainApplication.projectModel.stateDialog.model.addTransaction(); - transactionDialog.selectFunction("getZ"); - transactionDialog.acceptAndClose(); - mainApplication.projectModel.stateDialog.acceptAndClose(); - wait(1); - mainApplication.mainContent.startQuickDebugging(); - if (!ts.waitForSignal(mainApplication.clientModel, "runComplete()", 5000)) - fail("not run"); - tryCompare(mainApplication.mainContent.rightPane.transactionLog.transactionModel, "count", 4); - tryCompare(mainApplication.mainContent.rightPane.transactionLog.transactionModel.get(3), "returned", "(442)"); + if (el.contentItem) + el = el.contentItem; + ts.mouseClick(el, x, y, Qt.LeftButton, Qt.NoModifier, 10) } - Application - { - id: mainApplication - } + function test_tutorial() { TestTutorial.test_tutorial(); } + function test_dbg_defaultTransactionSequence() { TestDebugger.test_defaultTransactionSequence(); } + function test_dbg_transactionWithParameter() { TestDebugger.test_transactionWithParameter(); } + function test_dbg_constructorParameters() { TestDebugger.test_constructorParameters(); } + function test_dbg_arrayParametersAndStorage() { TestDebugger.test_arrayParametersAndStorage(); } } diff --git a/mix/test/qml/js/TestDebugger.js b/mix/test/qml/js/TestDebugger.js new file mode 100644 index 000000000..205c84816 --- /dev/null +++ b/mix/test/qml/js/TestDebugger.js @@ -0,0 +1,140 @@ +function test_defaultTransactionSequence() +{ + newProject(); + editContract( + "contract Contract {\r" + + " function Contract() {\r" + + " uint x = 69;\r" + + " uint y = 5;\r" + + " for (uint i = 0; i < y; ++i) {\r" + + " x += 42;\r" + + " z += x;\r" + + " }\r" + + " }\r" + + " uint z;\r" + + "}\r" + ); + if (!ts.waitForSignal(mainApplication.clientModel, "runComplete()", 5000)) + fail("Error running transaction"); + tryCompare(mainApplication.mainContent.rightPane.transactionLog.transactionModel, "count", 3); +} + +function test_transactionWithParameter() +{ + newProject(); + editContract( + "contract Contract {\r" + + " function setZ(uint256 x) {\r" + + " z = x;\r" + + " }\r" + + " function getZ() returns(uint256) {\r" + + " return z;\r" + + " }\r" + + " uint z;\r" + + "}\r" + ); + mainApplication.projectModel.stateListModel.editState(0); + mainApplication.projectModel.stateDialog.model.addTransaction(); + var transactionDialog = mainApplication.projectModel.stateDialog.transactionDialog; + ts.waitForRendering(transactionDialog, 3000); + transactionDialog.selectFunction("setZ"); + clickElement(transactionDialog, 140, 300); + ts.typeString("442", transactionDialog); + transactionDialog.acceptAndClose(); + mainApplication.projectModel.stateDialog.model.addTransaction(); + ts.waitForRendering(transactionDialog, 3000); + transactionDialog.selectFunction("getZ"); + transactionDialog.acceptAndClose(); + mainApplication.projectModel.stateDialog.acceptAndClose(); + mainApplication.mainContent.startQuickDebugging(); + if (!ts.waitForSignal(mainApplication.clientModel, "runComplete()", 5000)) + fail("Error running transaction"); + tryCompare(mainApplication.mainContent.rightPane.transactionLog.transactionModel, "count", 5); + tryCompare(mainApplication.mainContent.rightPane.transactionLog.transactionModel.get(4), "returned", "(442)"); +} + +function test_constructorParameters() +{ + newProject(); + editContract( + "contract Contract {\r" + + " function Contract(uint256 x) {\r" + + " z = x;\r" + + " }\r" + + " function getZ() returns(uint256) {\r" + + " return z;\r" + + " }\r" + + " uint z;\r" + + "}\r" + ); + mainApplication.projectModel.stateListModel.editState(0); + mainApplication.projectModel.stateDialog.model.editTransaction(2); + var transactionDialog = mainApplication.projectModel.stateDialog.transactionDialog; + ts.waitForRendering(transactionDialog, 3000); + clickElement(transactionDialog, 140, 300); + ts.typeString("442", transactionDialog); + transactionDialog.acceptAndClose(); + mainApplication.projectModel.stateDialog.model.addTransaction(); + ts.waitForRendering(transactionDialog, 3000); + transactionDialog.selectFunction("getZ"); + transactionDialog.acceptAndClose(); + mainApplication.projectModel.stateDialog.acceptAndClose(); + mainApplication.mainContent.startQuickDebugging(); + if (!ts.waitForSignal(mainApplication.clientModel, "runComplete()", 5000)) + fail("Error running transaction"); + tryCompare(mainApplication.mainContent.rightPane.transactionLog.transactionModel, "count", 4); + tryCompare(mainApplication.mainContent.rightPane.transactionLog.transactionModel.get(3), "returned", "(442)"); +} + +function test_arrayParametersAndStorage() +{ + newProject(); + editContract( + " contract ArrayTest {\r" + + " function setM(uint256[] x) external\r" + + " {\r" + + " m = x;\r" + + " s = 5;\r" + + " }\r" + + " \r" + + " function setMV(uint72[5] x) external\r" + + " {\r" + + " mv = x;\r" + + " s = 42;\r" + + " }\r" + + " \r" + + " uint256[] m;\r" + + " uint72[5] mv;\r" + + " uint256 s;\r" + + " }\r"); + + mainApplication.projectModel.stateListModel.editState(0); + mainApplication.projectModel.stateDialog.model.addTransaction(); + var transactionDialog = mainApplication.projectModel.stateDialog.transactionDialog; + ts.waitForRendering(transactionDialog, 3000); + transactionDialog.selectFunction("setM"); + clickElement(transactionDialog, 140, 300); + ts.typeString("4,5,6,2,10", transactionDialog); + transactionDialog.acceptAndClose(); + mainApplication.projectModel.stateDialog.model.addTransaction(); + ts.waitForRendering(transactionDialog, 3000); + transactionDialog.selectFunction("setMV"); + clickElement(transactionDialog, 140, 300); + ts.typeString("13,35,1,4", transactionDialog); + transactionDialog.acceptAndClose(); + mainApplication.projectModel.stateDialog.acceptAndClose(); + mainApplication.mainContent.startQuickDebugging(); + if (!ts.waitForSignal(mainApplication.clientModel, "debugDataReady(QObject*)", 5000)) + fail("Error running transaction"); + //debug setM + mainApplication.clientModel.debugRecord(3); + mainApplication.mainContent.rightPane.debugSlider.value = mainApplication.mainContent.rightPane.debugSlider.maximumValue; + tryCompare(mainApplication.mainContent.rightPane.solStorage.item.value, "m", ["4","5","6","2","10"]); + tryCompare(mainApplication.mainContent.rightPane.solStorage.item.value, "s", "5"); + //debug setMV + mainApplication.clientModel.debugRecord(4); + mainApplication.mainContent.rightPane.debugSlider.value = mainApplication.mainContent.rightPane.debugSlider.maximumValue - 1; + tryCompare(mainApplication.mainContent.rightPane.solStorage.item.value, "mv", ["13","35","1","4","0"]); + tryCompare(mainApplication.mainContent.rightPane.solStorage.item.value, "s", "42"); + tryCompare(mainApplication.mainContent.rightPane.solCallStack.listModel, 0, "setMV"); +} diff --git a/mix/test/qml/js/TestTutorial.js b/mix/test/qml/js/TestTutorial.js new file mode 100644 index 000000000..895b5c9c1 --- /dev/null +++ b/mix/test/qml/js/TestTutorial.js @@ -0,0 +1,71 @@ +//Test case to cover Mix tutorial + +function test_tutorial() +{ + newProject(); + editContract( + "contract Rating {\r" + + " function setRating(bytes32 _key, uint256 _value) {\r" + + " ratings[_key] = _value;\r" + + " }\r" + + " mapping (bytes32 => uint256) public ratings;\r" + + "}\r" + ); + editHtml( + "\r" + + "\r" + + "\r" + + "\r" + + "\r" + + "\r" + + "

Ratings

\r" + + "
\r" + + " Store:\r" + + " \r" + + " \r" + + " \r" + + "
\r" + + "
\r" + + " Query:\r" + + " \r" + + "
\r" + + "
\r" + + "\r" + + "\r" + ); + + mainApplication.projectModel.stateListModel.editState(0); + mainApplication.projectModel.stateDialog.model.addTransaction(); + var transactionDialog = mainApplication.projectModel.stateDialog.transactionDialog; + ts.waitForRendering(transactionDialog, 3000); + transactionDialog.selectFunction("setRating"); + clickElement(transactionDialog, 200, 310); + ts.typeString("Titanic", transactionDialog); + clickElement(transactionDialog, 200, 330); + ts.typeString("2", transactionDialog); + transactionDialog.acceptAndClose(); + mainApplication.projectModel.stateDialog.acceptAndClose(); + mainApplication.mainContent.startQuickDebugging(); + if (!ts.waitForSignal(mainApplication.clientModel, "debugDataReady(QObject*)", 5000)) + fail("Error running transaction"); + wait(1); + clickElement(mainApplication.mainContent.webView.webView, 1, 1); + ts.typeString("\t\t\t\t"); + ts.typeString("Titanic"); + tryCompare(mainApplication.mainContent.rightPane.transactionLog.callModel, "count", 8); //wait for 8 calls + mainApplication.mainContent.webView.getContent(); + ts.waitForSignal(mainApplication.mainContent.webView, "webContentReady()", 5000); + var body = mainApplication.mainContent.webView.webContent; + verify(body.indexOf("
2
") != -1, "Web content not updated") +} diff --git a/neth/main.cpp b/neth/main.cpp index 7ee4962e9..7d7f95b89 100644 --- a/neth/main.cpp +++ b/neth/main.cpp @@ -40,6 +40,7 @@ #include #include #endif +#include #include "BuildInfo.h" #undef KEY_EVENT // from windows.h @@ -76,6 +77,7 @@ void help() << " -c,--client-name Add a name to your client's version string (default: blank)." << endl << " -d,--db-path Load database from path (default: ~/.ethereum " << endl << " /Etherum or Library/Application Support/Ethereum)." << endl + << " -D,--initdag Initialize DAG for mining and exit." << endl << " -e,--ether-price Set the ether price in the reference unit e.g. ¢ (Default: 30.679)." << endl << " -f,--force-mining Mine even when there are no transaction to mine (Default: off)" << endl << " -h,--help Show this help message and exit." << endl @@ -321,6 +323,7 @@ enum class NodeMode int main(int argc, char** argv) { + bool initDAG = true; string listenIP; unsigned short listenPort = 30303; string publicIP; @@ -424,6 +427,8 @@ int main(int argc, char** argv) structuredLogging = true; else if ((arg == "-d" || arg == "--path" || arg == "--db-path") && i + 1 < argc) dbPath = argv[++i]; + else if (arg == "-D" || arg == "--initdag") + initDAG = true; else if ((arg == "-B" || arg == "--block-fees") && i + 1 < argc) { try @@ -556,6 +561,14 @@ int main(int argc, char** argv) &nodesState, miners ); + + if (initDAG) + { + cout << "Initialize DAG. (This will take awhile)" << endl; + Ethasher::get()->full(web3.ethereum()->blockChain().info()); + return 0; + } + web3.setIdealPeerCount(peers); std::shared_ptr gasPricer = make_shared(u256(double(ether / 1000) / etherPrice), u256(blockFees * 1000)); eth::Client* c = mode == NodeMode::Full ? web3.ethereum() : nullptr; diff --git a/test/SolidityOptimizer.cpp b/test/SolidityOptimizer.cpp index 4fedd642d..f523847f1 100644 --- a/test/SolidityOptimizer.cpp +++ b/test/SolidityOptimizer.cpp @@ -56,8 +56,16 @@ public: m_nonOptimizedContract = m_contractAddress; m_optimize = true; bytes optimizedBytecode = compileAndRun(_sourceCode, _value, _contractName); + size_t nonOptimizedSize = 0; + eth::eachInstruction(nonOptimizedBytecode, [&](Instruction, u256 const&) { + nonOptimizedSize++; + }); + size_t optimizedSize = 0; + eth::eachInstruction(optimizedBytecode, [&](Instruction, u256 const&) { + optimizedSize++; + }); BOOST_CHECK_MESSAGE( - nonOptimizedBytecode.size() > optimizedBytecode.size(), + nonOptimizedSize > optimizedSize, "Optimizer did not reduce bytecode size." ); m_optimizedContract = m_contractAddress; @@ -75,11 +83,16 @@ public: "\nOptimized: " + toHex(optimizedOutput)); } - void checkCSE(AssemblyItems const& _input, AssemblyItems const& _expectation) + AssemblyItems getCSE(AssemblyItems const& _input) { eth::CommonSubexpressionEliminator cse; BOOST_REQUIRE(cse.feedItems(_input.begin(), _input.end()) == _input.end()); - AssemblyItems output = cse.getOptimizedItems(); + return cse.getOptimizedItems(); + } + + void checkCSE(AssemblyItems const& _input, AssemblyItems const& _expectation) + { + AssemblyItems output = getCSE(_input); BOOST_CHECK_EQUAL_COLLECTIONS(_expectation.begin(), _expectation.end(), output.begin(), output.end()); } @@ -569,6 +582,155 @@ BOOST_AUTO_TEST_CASE(cse_jumpi_jump) }); } +BOOST_AUTO_TEST_CASE(cse_empty_sha3) +{ + AssemblyItems input{ + u256(0), + Instruction::DUP2, + Instruction::SHA3 + }; + checkCSE(input, { + u256(sha3(bytesConstRef())) + }); +} + +BOOST_AUTO_TEST_CASE(cse_partial_sha3) +{ + AssemblyItems input{ + u256(0xabcd) << (256 - 16), + u256(0), + Instruction::MSTORE, + u256(2), + u256(0), + Instruction::SHA3 + }; + checkCSE(input, { + u256(0xabcd) << (256 - 16), + u256(0), + Instruction::MSTORE, + u256(sha3(bytes{0xab, 0xcd})) + }); +} + +BOOST_AUTO_TEST_CASE(cse_sha3_twice_same_location) +{ + // sha3 twice from same dynamic location + AssemblyItems input{ + Instruction::DUP2, + Instruction::DUP1, + Instruction::MSTORE, + u256(64), + Instruction::DUP2, + Instruction::SHA3, + u256(64), + Instruction::DUP3, + Instruction::SHA3 + }; + checkCSE(input, { + Instruction::DUP2, + Instruction::DUP1, + Instruction::MSTORE, + u256(64), + Instruction::DUP2, + Instruction::SHA3, + Instruction::DUP1 + }); +} + +BOOST_AUTO_TEST_CASE(cse_sha3_twice_same_content) +{ + // sha3 twice from different dynamic location but with same content + AssemblyItems input{ + Instruction::DUP1, + u256(0x80), + Instruction::MSTORE, // m[128] = DUP1 + u256(0x20), + u256(0x80), + Instruction::SHA3, // sha3(m[128..(128+32)]) + Instruction::DUP2, + u256(12), + Instruction::MSTORE, // m[12] = DUP1 + u256(0x20), + u256(12), + Instruction::SHA3 // sha3(m[12..(12+32)]) + }; + checkCSE(input, { + u256(0x80), + Instruction::DUP2, + Instruction::DUP2, + Instruction::MSTORE, + u256(0x20), + Instruction::SWAP1, + Instruction::SHA3, + u256(12), + Instruction::DUP3, + Instruction::SWAP1, + Instruction::MSTORE, + Instruction::DUP1 + }); +} + +BOOST_AUTO_TEST_CASE(cse_sha3_twice_same_content_dynamic_store_in_between) +{ + // sha3 twice from different dynamic location but with same content, + // dynamic mstore in between, which forces us to re-calculate the sha3 + AssemblyItems input{ + u256(0x80), + Instruction::DUP2, + Instruction::DUP2, + Instruction::MSTORE, // m[128] = DUP1 + u256(0x20), + Instruction::DUP1, + Instruction::DUP3, + Instruction::SHA3, // sha3(m[128..(128+32)]) + u256(12), + Instruction::DUP5, + Instruction::DUP2, + Instruction::MSTORE, // m[12] = DUP1 + Instruction::DUP12, + Instruction::DUP14, + Instruction::MSTORE, // destroys memory knowledge + Instruction::SWAP2, + Instruction::SWAP1, + Instruction::SWAP2, + Instruction::SHA3 // sha3(m[12..(12+32)]) + }; + checkCSE(input, input); +} + +BOOST_AUTO_TEST_CASE(cse_sha3_twice_same_content_noninterfering_store_in_between) +{ + // sha3 twice from different dynamic location but with same content, + // dynamic mstore in between, but does not force us to re-calculate the sha3 + AssemblyItems input{ + u256(0x80), + Instruction::DUP2, + Instruction::DUP2, + Instruction::MSTORE, // m[128] = DUP1 + u256(0x20), + Instruction::DUP1, + Instruction::DUP3, + Instruction::SHA3, // sha3(m[128..(128+32)]) + u256(12), + Instruction::DUP5, + Instruction::DUP2, + Instruction::MSTORE, // m[12] = DUP1 + Instruction::DUP12, + u256(12 + 32), + Instruction::MSTORE, // does not destoy memory knowledge + Instruction::DUP13, + u256(128 - 32), + Instruction::MSTORE, // does not destoy memory knowledge + u256(0x20), + u256(12), + Instruction::SHA3 // sha3(m[12..(12+32)]) + }; + // if this changes too often, only count the number of SHA3 and MSTORE instructions + AssemblyItems output = getCSE(input); + BOOST_CHECK_EQUAL(4, count(output.begin(), output.end(), AssemblyItem(Instruction::MSTORE))); + BOOST_CHECK_EQUAL(1, count(output.begin(), output.end(), AssemblyItem(Instruction::SHA3))); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/peer.cpp b/test/peer.cpp index 904de0e9d..2aeb99469 100644 --- a/test/peer.cpp +++ b/test/peer.cpp @@ -75,7 +75,7 @@ BOOST_AUTO_TEST_CASE(save_nodes) h->setIdealPeerCount(10); // starting host is required so listenport is available h->start(); - while (!h->isStarted()) + while (!h->haveNetwork()) this_thread::sleep_for(chrono::milliseconds(2)); hosts.push_back(h); }