diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index c40e8f6a0..39bc0ad43 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -1458,7 +1458,7 @@ void Main::on_debugCurrent_triggered() } } -void Main::on_debugDumpState_triggered(int _add) +void Main::debugDumpState(int _add) { if (auto item = ui->blocks->currentItem()) { @@ -1479,11 +1479,6 @@ void Main::on_debugDumpState_triggered(int _add) } } -void Main::on_debugDumpStatePre_triggered() -{ - on_debugDumpState_triggered(0); -} - void Main::on_contracts_currentItemChanged() { ui->contractInfo->clear(); @@ -1511,7 +1506,7 @@ void Main::on_contracts_currentItemChanged() } } -void Main::on_idealPeers_valueChanged() +void Main::on_idealPeers_valueChanged(int) { m_webThree->setIdealPeerCount(ui->idealPeers->value()); } @@ -1523,11 +1518,11 @@ void Main::on_ourAccounts_doubleClicked() qApp->clipboard()->setText(QString::fromStdString(toHex(h.asArray()))); } -void Main::on_log_doubleClicked() +/*void Main::on_log_doubleClicked() { ui->log->setPlainText(""); m_logHistory.clear(); -} +}*/ void Main::on_accounts_doubleClicked() { diff --git a/alethzero/MainWin.h b/alethzero/MainWin.h index 52b98d293..638469fbe 100644 --- a/alethzero/MainWin.h +++ b/alethzero/MainWin.h @@ -109,7 +109,7 @@ private slots: void on_go_triggered(); void on_net_triggered(); void on_connect_triggered(); - void on_idealPeers_valueChanged(); + void on_idealPeers_valueChanged(int); // Mining void on_mine_triggered(); @@ -142,7 +142,7 @@ private slots: void on_blocks_currentItemChanged(); // Logging - void on_log_doubleClicked(); +// void on_log_doubleClicked(); void on_verbosity_valueChanged(); // Misc @@ -162,8 +162,8 @@ private slots: // Debugger void on_debugCurrent_triggered(); - void on_debugDumpState_triggered(int _add = 1); - void on_debugDumpStatePre_triggered(); + void on_debugDumpState_triggered() { debugDumpState(1); } + void on_debugDumpStatePre_triggered() { debugDumpState(0); } // Whisper void on_newIdentity_triggered(); @@ -177,6 +177,8 @@ signals: void poll(); private: + void debugDumpState(int _add); + dev::p2p::NetworkPreferences netPrefs() const; QString lookup(QString const& _n) const; diff --git a/alethzero/Transact.cpp b/alethzero/Transact.cpp index 1b9a62a2d..c59c80c39 100644 --- a/alethzero/Transact.cpp +++ b/alethzero/Transact.cpp @@ -55,7 +55,7 @@ Transact::Transact(Context* _c, QWidget* _parent): ui->valueUnits->setCurrentIndex(6); ui->gasPriceUnits->setCurrentIndex(4); ui->gasPrice->setValue(10); - on_destination_currentTextChanged(); + on_destination_currentTextChanged(QString()); } Transact::~Transact() @@ -147,7 +147,7 @@ string Transact::getFunctionHashes(dev::solidity::CompilerStack const& _compiler return ret; } -void Transact::on_destination_currentTextChanged() +void Transact::on_destination_currentTextChanged(QString) { if (ui->destination->currentText().size() && ui->destination->currentText() != "(Create Contract)") if (Address a = m_context->fromString(ui->destination->currentText())) diff --git a/alethzero/Transact.h b/alethzero/Transact.h index afb45f62d..f14005eff 100644 --- a/alethzero/Transact.h +++ b/alethzero/Transact.h @@ -44,12 +44,12 @@ public: void setEnvironment(QList _myKeys, dev::eth::Client* _eth, NatSpecFace* _natSpecDB); private slots: - void on_destination_currentTextChanged(); - void on_value_valueChanged() { updateFee(); } - void on_gas_valueChanged() { updateFee(); } - void on_valueUnits_currentIndexChanged() { updateFee(); } - void on_gasPriceUnits_currentIndexChanged() { updateFee(); } - void on_gasPrice_valueChanged() { updateFee(); } + void on_destination_currentTextChanged(QString); + void on_value_valueChanged(int) { updateFee(); } + void on_gas_valueChanged(int) { updateFee(); } + void on_valueUnits_currentIndexChanged(int) { updateFee(); } + void on_gasPriceUnits_currentIndexChanged(int) { updateFee(); } + void on_gasPrice_valueChanged(int) { updateFee(); } void on_data_textChanged() { rejigData(); } void on_optimize_clicked() { rejigData(); } void on_send_clicked(); diff --git a/evmjit/libevmjit/ExecStats.h b/evmjit/libevmjit/ExecStats.h index 498e21341..1ac9b6995 100644 --- a/evmjit/libevmjit/ExecStats.h +++ b/evmjit/libevmjit/ExecStats.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include "ExecutionEngine.h" diff --git a/evmjit/libevmjit/preprocessor/llvm_includes_end.h b/evmjit/libevmjit/preprocessor/llvm_includes_end.h index 023c8021e..2ead6dda3 100644 --- a/evmjit/libevmjit/preprocessor/llvm_includes_end.h +++ b/evmjit/libevmjit/preprocessor/llvm_includes_end.h @@ -1,5 +1,7 @@ #if defined(_MSC_VER) #pragma warning(pop) +#elif defined(__clang__) + #pragma clang diagnostic pop #else #pragma GCC diagnostic pop #endif diff --git a/evmjit/libevmjit/preprocessor/llvm_includes_start.h b/evmjit/libevmjit/preprocessor/llvm_includes_start.h index bf34ade99..9077bf43f 100644 --- a/evmjit/libevmjit/preprocessor/llvm_includes_start.h +++ b/evmjit/libevmjit/preprocessor/llvm_includes_start.h @@ -1,6 +1,10 @@ #if defined(_MSC_VER) #pragma warning(push) #pragma warning(disable: 4267 4244 4800) +#elif defined(__clang__) + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wunused-parameter" + #pragma clang diagnostic ignored "-Wconversion" #else #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" diff --git a/libsolidity/AST.cpp b/libsolidity/AST.cpp index a18785ae1..179461152 100644 --- a/libsolidity/AST.cpp +++ b/libsolidity/AST.cpp @@ -609,13 +609,48 @@ void MemberAccess::checkTypeRequirements() void IndexAccess::checkTypeRequirements() { m_base->checkTypeRequirements(); - if (m_base->getType()->getCategory() != Type::Category::Mapping) - BOOST_THROW_EXCEPTION(m_base->createTypeError("Indexed expression has to be a mapping (is " + - m_base->getType()->toString() + ")")); - MappingType const& type = dynamic_cast(*m_base->getType()); - m_index->expectType(*type.getKeyType()); - m_type = type.getValueType(); - m_isLValue = true; + switch (m_base->getType()->getCategory()) + { + case Type::Category::Array: + { + ArrayType const& type = dynamic_cast(*m_base->getType()); + if (!m_index) + BOOST_THROW_EXCEPTION(createTypeError("Index expression cannot be omitted.")); + m_index->expectType(IntegerType(256)); + m_type = type.getBaseType(); + m_isLValue = true; + break; + } + case Type::Category::Mapping: + { + MappingType const& type = dynamic_cast(*m_base->getType()); + if (!m_index) + BOOST_THROW_EXCEPTION(createTypeError("Index expression cannot be omitted.")); + m_index->expectType(*type.getKeyType()); + m_type = type.getValueType(); + m_isLValue = true; + break; + } + case Type::Category::TypeType: + { + TypeType const& type = dynamic_cast(*m_base->getType()); + if (!m_index) + m_type = make_shared(make_shared(ArrayType::Location::Memory, type.getActualType())); + else + { + m_index->checkTypeRequirements(); + auto length = dynamic_cast(m_index->getType().get()); + if (!length) + BOOST_THROW_EXCEPTION(m_index->createTypeError("Integer constant expected.")); + m_type = make_shared(make_shared( + ArrayType::Location::Memory, type.getActualType(), length->literalValue(nullptr))); + } + break; + } + default: + BOOST_THROW_EXCEPTION(m_base->createTypeError( + "Indexed expression has to be a type, mapping or array (is " + m_base->getType()->toString() + ")")); + } } void Identifier::checkTypeRequirements() diff --git a/libsolidity/AST.h b/libsolidity/AST.h index 138abf36e..60648cdc2 100644 --- a/libsolidity/AST.h +++ b/libsolidity/AST.h @@ -441,7 +441,7 @@ public: virtual void accept(ASTVisitor& _visitor) override; virtual void accept(ASTConstVisitor& _visitor) const override; - TypeName const* getTypeName() const { return m_typeName.get(); } + TypeName* getTypeName() { return m_typeName.get(); } ASTPointer const& getValue() const { return m_value; } /// Returns the declared or inferred type. Can be an empty pointer if no type was explicitly @@ -588,7 +588,7 @@ public: /// Retrieve the element of the type hierarchy this node refers to. Can return an empty shared /// pointer until the types have been resolved using the @ref NameAndTypeResolver. /// If it returns an empty shared pointer after that, this indicates that the type was not found. - virtual std::shared_ptr toType() const = 0; + virtual std::shared_ptr toType() = 0; }; /** @@ -605,7 +605,7 @@ public: } virtual void accept(ASTVisitor& _visitor) override; virtual void accept(ASTConstVisitor& _visitor) const override; - virtual std::shared_ptr toType() const override { return Type::fromElementaryTypeName(m_type); } + virtual std::shared_ptr toType() override { return Type::fromElementaryTypeName(m_type); } Token::Value getTypeName() const { return m_type; } @@ -623,7 +623,7 @@ public: TypeName(_location), m_name(_name), m_referencedDeclaration(nullptr) {} virtual void accept(ASTVisitor& _visitor) override; virtual void accept(ASTConstVisitor& _visitor) const override; - virtual std::shared_ptr toType() const override { return Type::fromUserDefinedTypeName(*this); } + virtual std::shared_ptr toType() override { return Type::fromUserDefinedTypeName(*this); } ASTString const& getName() const { return *m_name; } void setReferencedDeclaration(Declaration const& _referencedDeclaration) { m_referencedDeclaration = &_referencedDeclaration; } @@ -646,7 +646,7 @@ public: TypeName(_location), m_keyType(_keyType), m_valueType(_valueType) {} virtual void accept(ASTVisitor& _visitor) override; virtual void accept(ASTConstVisitor& _visitor) const override; - virtual std::shared_ptr toType() const override { return Type::fromMapping(*this); } + virtual TypePointer toType() override { return Type::fromMapping(*m_keyType, *m_valueType); } ElementaryTypeName const& getKeyType() const { return *m_keyType; } TypeName const& getValueType() const { return *m_valueType; } @@ -656,6 +656,27 @@ private: ASTPointer m_valueType; }; +/** + * An array type, can be "typename[]" or "typename[]". + */ +class ArrayTypeName: public TypeName +{ +public: + ArrayTypeName(Location const& _location, ASTPointer const& _baseType, + ASTPointer const& _length): + TypeName(_location), m_baseType(_baseType), m_length(_length) {} + virtual void accept(ASTVisitor& _visitor) override; + virtual void accept(ASTConstVisitor& _visitor) const override; + virtual std::shared_ptr toType() override { return Type::fromArrayTypeName(*m_baseType, m_length.get()); } + + TypeName const& getBaseType() const { return *m_baseType; } + Expression const* getLength() const { return m_length.get(); } + +private: + ASTPointer m_baseType; + ASTPointer m_length; ///< Length of the array, might be empty. +}; + /// @} /// Statements @@ -1081,7 +1102,7 @@ public: virtual void checkTypeRequirements() override; Expression const& getBaseExpression() const { return *m_base; } - Expression const& getIndexExpression() const { return *m_index; } + Expression const* getIndexExpression() const { return m_index.get(); } private: ASTPointer m_base; diff --git a/libsolidity/ASTForward.h b/libsolidity/ASTForward.h index 3a151b63e..0ba485a2f 100644 --- a/libsolidity/ASTForward.h +++ b/libsolidity/ASTForward.h @@ -53,6 +53,7 @@ class TypeName; class ElementaryTypeName; class UserDefinedTypeName; class Mapping; +class ArrayTypeName; class Statement; class Block; class PlaceholderStatement; diff --git a/libsolidity/ASTPrinter.cpp b/libsolidity/ASTPrinter.cpp index 209bb73e6..5bcc46df7 100644 --- a/libsolidity/ASTPrinter.cpp +++ b/libsolidity/ASTPrinter.cpp @@ -155,6 +155,13 @@ bool ASTPrinter::visit(Mapping const& _node) return goDeeper(); } +bool ASTPrinter::visit(ArrayTypeName const& _node) +{ + writeLine("ArrayTypeName"); + printSourcePart(_node); + return goDeeper(); +} + bool ASTPrinter::visit(Statement const& _node) { writeLine("Statement"); @@ -419,6 +426,11 @@ void ASTPrinter::endVisit(Mapping const&) m_indentation--; } +void ASTPrinter::endVisit(ArrayTypeName const&) +{ + m_indentation--; +} + void ASTPrinter::endVisit(Statement const&) { m_indentation--; diff --git a/libsolidity/ASTPrinter.h b/libsolidity/ASTPrinter.h index 7a0ef5a65..a1797383c 100644 --- a/libsolidity/ASTPrinter.h +++ b/libsolidity/ASTPrinter.h @@ -58,6 +58,7 @@ public: bool visit(ElementaryTypeName const& _node) override; bool visit(UserDefinedTypeName const& _node) override; bool visit(Mapping const& _node) override; + bool visit(ArrayTypeName const& _node) override; bool visit(Statement const& _node) override; bool visit(Block const& _node) override; bool visit(PlaceholderStatement const& _node) override; @@ -99,6 +100,7 @@ public: void endVisit(ElementaryTypeName const&) override; void endVisit(UserDefinedTypeName const&) override; void endVisit(Mapping const&) override; + void endVisit(ArrayTypeName const&) override; void endVisit(Statement const&) override; void endVisit(Block const&) override; void endVisit(PlaceholderStatement const&) override; diff --git a/libsolidity/ASTVisitor.h b/libsolidity/ASTVisitor.h index 2ecfbe4b1..3eeb9c456 100644 --- a/libsolidity/ASTVisitor.h +++ b/libsolidity/ASTVisitor.h @@ -59,6 +59,7 @@ public: virtual bool visit(ElementaryTypeName&) { return true; } virtual bool visit(UserDefinedTypeName&) { return true; } virtual bool visit(Mapping&) { return true; } + virtual bool visit(ArrayTypeName&) { return true; } virtual bool visit(Statement&) { return true; } virtual bool visit(Block&) { return true; } virtual bool visit(PlaceholderStatement&) { return true; } @@ -102,6 +103,7 @@ public: virtual void endVisit(ElementaryTypeName&) { } virtual void endVisit(UserDefinedTypeName&) { } virtual void endVisit(Mapping&) { } + virtual void endVisit(ArrayTypeName&) { } virtual void endVisit(Statement&) { } virtual void endVisit(Block&) { } virtual void endVisit(PlaceholderStatement&) { } @@ -149,6 +151,7 @@ public: virtual bool visit(ElementaryTypeName const&) { return true; } virtual bool visit(UserDefinedTypeName const&) { return true; } virtual bool visit(Mapping const&) { return true; } + virtual bool visit(ArrayTypeName const&) { return true; } virtual bool visit(Statement const&) { return true; } virtual bool visit(Block const&) { return true; } virtual bool visit(PlaceholderStatement const&) { return true; } @@ -192,6 +195,7 @@ public: virtual void endVisit(ElementaryTypeName const&) { } virtual void endVisit(UserDefinedTypeName const&) { } virtual void endVisit(Mapping const&) { } + virtual void endVisit(ArrayTypeName const&) { } virtual void endVisit(Statement const&) { } virtual void endVisit(Block const&) { } virtual void endVisit(PlaceholderStatement const&) { } diff --git a/libsolidity/AST_accept.h b/libsolidity/AST_accept.h index 5bd6993db..81ede8fc9 100644 --- a/libsolidity/AST_accept.h +++ b/libsolidity/AST_accept.h @@ -327,6 +327,28 @@ void Mapping::accept(ASTConstVisitor& _visitor) const _visitor.endVisit(*this); } +void ArrayTypeName::accept(ASTVisitor& _visitor) +{ + if (_visitor.visit(*this)) + { + m_baseType->accept(_visitor); + if (m_length) + m_length->accept(_visitor); + } + _visitor.endVisit(*this); +} + +void ArrayTypeName::accept(ASTConstVisitor& _visitor) const +{ + if (_visitor.visit(*this)) + { + m_baseType->accept(_visitor); + if (m_length) + m_length->accept(_visitor); + } + _visitor.endVisit(*this); +} + void Block::accept(ASTVisitor& _visitor) { if (_visitor.visit(*this)) @@ -604,7 +626,8 @@ void IndexAccess::accept(ASTVisitor& _visitor) if (_visitor.visit(*this)) { m_base->accept(_visitor); - m_index->accept(_visitor); + if (m_index) + m_index->accept(_visitor); } _visitor.endVisit(*this); } @@ -614,7 +637,8 @@ void IndexAccess::accept(ASTConstVisitor& _visitor) const if (_visitor.visit(*this)) { m_base->accept(_visitor); - m_index->accept(_visitor); + if (m_index) + m_index->accept(_visitor); } _visitor.endVisit(*this); } diff --git a/libsolidity/CompilerStack.h b/libsolidity/CompilerStack.h index 8f77ef68a..812f41863 100644 --- a/libsolidity/CompilerStack.h +++ b/libsolidity/CompilerStack.h @@ -60,7 +60,7 @@ class CompilerStack: boost::noncopyable { public: /// Creates a new compiler stack. Adds standard sources if @a _addStandardSources. - explicit CompilerStack(bool _addStandardSources = false); + explicit CompilerStack(bool _addStandardSources = true); /// Adds a source object (e.g. file) to the parser. After this, parse has to be called again. /// @returns true if a source object by the name already existed and was replaced. diff --git a/libsolidity/CompilerUtils.cpp b/libsolidity/CompilerUtils.cpp index 047bc6d62..c7ce94456 100644 --- a/libsolidity/CompilerUtils.cpp +++ b/libsolidity/CompilerUtils.cpp @@ -36,14 +36,14 @@ const unsigned int CompilerUtils::dataStartOffset = 4; unsigned CompilerUtils::loadFromMemory(unsigned _offset, Type const& _type, bool _fromCalldata, bool _padToWordBoundaries) { - solAssert(_type.getCategory() != Type::Category::ByteArray, "Unable to statically load dynamic type."); + solAssert(_type.getCategory() != Type::Category::Array, "Unable to statically load dynamic type."); m_context << u256(_offset); return loadFromMemoryHelper(_type, _fromCalldata, _padToWordBoundaries); } void CompilerUtils::loadFromMemoryDynamic(Type const& _type, bool _fromCalldata, bool _padToWordBoundaries) { - solAssert(_type.getCategory() != Type::Category::ByteArray, "Byte arrays not yet implemented."); + solAssert(_type.getCategory() != Type::Category::Array, "Arrays not yet implemented."); m_context << eth::Instruction::DUP1; unsigned numBytes = loadFromMemoryHelper(_type, _fromCalldata, _padToWordBoundaries); // update memory counter @@ -55,7 +55,7 @@ void CompilerUtils::loadFromMemoryDynamic(Type const& _type, bool _fromCalldata, unsigned CompilerUtils::storeInMemory(unsigned _offset, Type const& _type, bool _padToWordBoundaries) { - solAssert(_type.getCategory() != Type::Category::ByteArray, "Unable to statically store dynamic type."); + solAssert(_type.getCategory() != Type::Category::Array, "Unable to statically store dynamic type."); unsigned numBytes = prepareMemoryStore(_type, _padToWordBoundaries); if (numBytes > 0) m_context << u256(_offset) << eth::Instruction::MSTORE; @@ -64,11 +64,12 @@ unsigned CompilerUtils::storeInMemory(unsigned _offset, Type const& _type, bool void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBoundaries) { - if (_type.getCategory() == Type::Category::ByteArray) + if (_type.getCategory() == Type::Category::Array) { - auto const& type = dynamic_cast(_type); + auto const& type = dynamic_cast(_type); + solAssert(type.isByteArray(), "Non byte arrays not yet implemented here."); - if (type.getLocation() == ByteArrayType::Location::CallData) + if (type.getLocation() == ArrayType::Location::CallData) { // stack: target source_offset source_len m_context << eth::Instruction::DUP1 << eth::Instruction::DUP3 << eth::Instruction::DUP5 @@ -79,7 +80,7 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound } else { - solAssert(type.getLocation() == ByteArrayType::Location::Storage, "Memory byte arrays not yet implemented."); + solAssert(type.getLocation() == ArrayType::Location::Storage, "Memory byte arrays not yet implemented."); m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD; // stack here: memory_offset storage_offset length_bytes // jump to end if length is zero @@ -163,16 +164,18 @@ void CompilerUtils::computeHashStatic(Type const& _type, bool _padToWordBoundari m_context << u256(length) << u256(0) << eth::Instruction::SHA3; } -void CompilerUtils::copyByteArrayToStorage(ByteArrayType const& _targetType, - ByteArrayType const& _sourceType) const +void CompilerUtils::copyByteArrayToStorage( + ArrayType const& _targetType, ArrayType const& _sourceType) const { // stack layout: [source_ref] target_ref (top) // need to leave target_ref on the stack at the end - solAssert(_targetType.getLocation() == ByteArrayType::Location::Storage, ""); + solAssert(_targetType.getLocation() == ArrayType::Location::Storage, ""); + solAssert(_targetType.isByteArray(), "Non byte arrays not yet implemented here."); + solAssert(_sourceType.isByteArray(), "Non byte arrays not yet implemented here."); switch (_sourceType.getLocation()) { - case ByteArrayType::Location::CallData: + case ArrayType::Location::CallData: { // This also assumes that after "length" we only have zeros, i.e. it cannot be used to // slice a byte array from calldata. @@ -224,7 +227,7 @@ void CompilerUtils::copyByteArrayToStorage(ByteArrayType const& _targetType, << eth::Instruction::POP << eth::Instruction::POP; break; } - case ByteArrayType::Location::Storage: + case ArrayType::Location::Storage: { // this copies source to target and also clears target if it was larger @@ -313,9 +316,10 @@ unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCallda return numBytes; } -void CompilerUtils::clearByteArray(ByteArrayType const& _type) const +void CompilerUtils::clearByteArray(ArrayType const& _type) const { - solAssert(_type.getLocation() == ByteArrayType::Location::Storage, ""); + solAssert(_type.getLocation() == ArrayType::Location::Storage, ""); + solAssert(_type.isByteArray(), "Non byte arrays not yet implemented here."); // fetch length m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD; diff --git a/libsolidity/CompilerUtils.h b/libsolidity/CompilerUtils.h index 5369d3bf2..2fb97d808 100644 --- a/libsolidity/CompilerUtils.h +++ b/libsolidity/CompilerUtils.h @@ -82,11 +82,11 @@ public: /// Copies a byte array to a byte array in storage. /// Stack pre: [source_reference] target_reference /// Stack post: target_reference - void copyByteArrayToStorage(ByteArrayType const& _targetType, ByteArrayType const& _sourceType) const; + void copyByteArrayToStorage(ArrayType const& _targetType, ArrayType const& _sourceType) const; /// Clears the length and data elements of the byte array referenced on the stack. /// Stack pre: reference /// Stack post: - void clearByteArray(ByteArrayType const& _type) const; + void clearByteArray(ArrayType const& _type) const; /// Bytes we need to the start of call data. /// - The size in bytes of the function (hash) identifier. diff --git a/libsolidity/ExpressionCompiler.cpp b/libsolidity/ExpressionCompiler.cpp index 461dfef14..94f65b93f 100644 --- a/libsolidity/ExpressionCompiler.cpp +++ b/libsolidity/ExpressionCompiler.cpp @@ -530,20 +530,21 @@ void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess) m_context << enumType->getMemberValue(_memberAccess.getMemberName()); break; } - case Type::Category::ByteArray: + case Type::Category::Array: { - solAssert(member == "length", "Illegal bytearray member."); - auto const& type = dynamic_cast(*_memberAccess.getExpression().getType()); + solAssert(member == "length", "Illegal array member."); + auto const& type = dynamic_cast(*_memberAccess.getExpression().getType()); + solAssert(type.isByteArray(), "Non byte arrays not yet implemented here."); switch (type.getLocation()) { - case ByteArrayType::Location::CallData: + case ArrayType::Location::CallData: m_context << eth::Instruction::SWAP1 << eth::Instruction::POP; break; - case ByteArrayType::Location::Storage: + case ArrayType::Location::Storage: m_context << eth::Instruction::SLOAD; break; default: - solAssert(false, "Unsupported byte array location."); + solAssert(false, "Unsupported array location."); break; } break; @@ -561,7 +562,8 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) solAssert(baseType.getCategory() == Type::Category::Mapping, ""); Type const& keyType = *dynamic_cast(baseType).getKeyType(); m_context << u256(0); - appendExpressionCopyToMemory(keyType, _indexAccess.getIndexExpression()); + solAssert(_indexAccess.getIndexExpression(), "Index expression expected."); + appendExpressionCopyToMemory(keyType, *_indexAccess.getIndexExpression()); solAssert(baseType.getSizeOnStack() == 1, "Unexpected: Not exactly one stack slot taken by subscriptable expression."); m_context << eth::Instruction::SWAP1; @@ -1135,11 +1137,11 @@ void ExpressionCompiler::LValue::storeValue(Type const& _sourceType, Location co else { solAssert(_sourceType.getCategory() == m_dataType->getCategory(), "Wrong type conversation for assignment."); - if (m_dataType->getCategory() == Type::Category::ByteArray) + if (m_dataType->getCategory() == Type::Category::Array) { CompilerUtils(*m_context).copyByteArrayToStorage( - dynamic_cast(*m_dataType), - dynamic_cast(_sourceType)); + dynamic_cast(*m_dataType), + dynamic_cast(_sourceType)); if (_move) *m_context << eth::Instruction::POP; } @@ -1210,8 +1212,8 @@ void ExpressionCompiler::LValue::setToZero(Location const& _location) const break; } case LValueType::Storage: - if (m_dataType->getCategory() == Type::Category::ByteArray) - CompilerUtils(*m_context).clearByteArray(dynamic_cast(*m_dataType)); + if (m_dataType->getCategory() == Type::Category::Array) + CompilerUtils(*m_context).clearByteArray(dynamic_cast(*m_dataType)); else if (m_dataType->getCategory() == Type::Category::Struct) { // stack layout: ref diff --git a/libsolidity/ExpressionCompiler.h b/libsolidity/ExpressionCompiler.h index 889c58b19..31bcc924a 100644 --- a/libsolidity/ExpressionCompiler.h +++ b/libsolidity/ExpressionCompiler.h @@ -39,7 +39,7 @@ namespace solidity { class CompilerContext; class Type; class IntegerType; -class ByteArrayType; +class ArrayType; class StaticStringType; /** @@ -165,7 +165,7 @@ private: /// Convenience function to retrieve Value from Storage. Specific version of @ref retrieveValue void retrieveValueFromStorage(bool _remove = false) const; /// Copies from a byte array to a byte array in storage, both references on the stack. - void copyByteArrayToStorage(ByteArrayType const& _targetType, ByteArrayType const& _sourceType) const; + void copyByteArrayToStorage(ArrayType const& _targetType, ArrayType const& _sourceType) const; CompilerContext* m_context; LValueType m_type = LValueType::None; diff --git a/libsolidity/NameAndTypeResolver.cpp b/libsolidity/NameAndTypeResolver.cpp index 15e1ac6f5..f6ee2f1d0 100644 --- a/libsolidity/NameAndTypeResolver.cpp +++ b/libsolidity/NameAndTypeResolver.cpp @@ -334,10 +334,10 @@ void ReferencesResolver::endVisit(VariableDeclaration& _variable) if (_variable.getTypeName()) { TypePointer type = _variable.getTypeName()->toType(); - // All byte array parameter types should point to call data + // All array parameter types should point to call data if (_variable.isExternalFunctionParameter()) - if (auto const* byteArrayType = dynamic_cast(type.get())) - type = byteArrayType->copyForLocation(ByteArrayType::Location::CallData); + if (auto const* arrayType = dynamic_cast(type.get())) + type = arrayType->copyForLocation(ArrayType::Location::CallData); _variable.setType(type); if (!_variable.getType()) diff --git a/libsolidity/Parser.cpp b/libsolidity/Parser.cpp index 9fe1af22e..1fc5ec98f 100644 --- a/libsolidity/Parser.cpp +++ b/libsolidity/Parser.cpp @@ -41,8 +41,11 @@ class Parser::ASTNodeFactory public: ASTNodeFactory(Parser const& _parser): m_parser(_parser), m_location(_parser.getPosition(), -1, _parser.getSourceName()) {} + ASTNodeFactory(Parser const& _parser, ASTPointer const& _childNode): + m_parser(_parser), m_location(_childNode->getLocation()) {} void markEndPosition() { m_location.end = m_parser.getEndPosition(); } + void setLocation(Location const& _location) { m_location = _location; } void setLocationEmpty() { m_location.end = m_location.start; } /// Set the end position to the one of the given node. void setEndPositionFromNode(ASTPointer const& _node) { m_location.end = _node->getLocation().end; } @@ -299,12 +302,20 @@ ASTPointer Parser::parseEnumDefinition() return nodeFactory.createNode(name, members); } -ASTPointer Parser::parseVariableDeclaration(VarDeclParserOptions const& _options) +ASTPointer Parser::parseVariableDeclaration( + VarDeclParserOptions const& _options, ASTPointer const& _lookAheadArrayType) { - ASTNodeFactory nodeFactory(*this); - ASTPointer type = parseTypeName(_options.allowVar); - if (type != nullptr) - nodeFactory.setEndPositionFromNode(type); + ASTNodeFactory nodeFactory = _lookAheadArrayType ? + ASTNodeFactory(*this, _lookAheadArrayType) : ASTNodeFactory(*this); + ASTPointer type; + if (_lookAheadArrayType) + type = _lookAheadArrayType; + else + { + type = parseTypeName(_options.allowVar); + if (type != nullptr) + nodeFactory.setEndPositionFromNode(type); + } bool isIndexed = false; ASTPointer identifier; Token::Value token = m_scanner->getCurrentToken(); @@ -407,6 +418,7 @@ ASTPointer Parser::parseIdentifier() ASTPointer Parser::parseTypeName(bool _allowVar) { + ASTNodeFactory nodeFactory(*this); ASTPointer type; Token::Value token = m_scanner->getCurrentToken(); if (Token::isElementaryTypeName(token)) @@ -421,9 +433,7 @@ ASTPointer Parser::parseTypeName(bool _allowVar) m_scanner->next(); } else if (token == Token::Mapping) - { type = parseMapping(); - } else if (token == Token::Identifier) { ASTNodeFactory nodeFactory(*this); @@ -432,6 +442,18 @@ ASTPointer Parser::parseTypeName(bool _allowVar) } else BOOST_THROW_EXCEPTION(createParserError("Expected type name")); + + // Parse "[...]" postfixes for arrays. + while (m_scanner->getCurrentToken() == Token::LBrack) + { + m_scanner->next(); + ASTPointer length; + if (m_scanner->getCurrentToken() != Token::RBrack) + length = parseExpression(); + nodeFactory.markEndPosition(); + expectToken(Token::RBrack); + type = nodeFactory.createNode(type, length); + } return type; } @@ -530,7 +552,7 @@ ASTPointer Parser::parseStatement() } // fall-through default: - statement = parseVarDeclOrExprStmt(); + statement = parseSimpleStatement(); } expectToken(Token::Semicolon); return statement; @@ -579,7 +601,7 @@ ASTPointer Parser::parseForStatement() // LTODO: Maybe here have some predicate like peekExpression() instead of checking for semicolon and RParen? if (m_scanner->getCurrentToken() != Token::Semicolon) - initExpression = parseVarDeclOrExprStmt(); + initExpression = parseSimpleStatement(); expectToken(Token::Semicolon); if (m_scanner->getCurrentToken() != Token::Semicolon) @@ -598,48 +620,89 @@ ASTPointer Parser::parseForStatement() body); } -ASTPointer Parser::parseVarDeclOrExprStmt() +ASTPointer Parser::parseSimpleStatement() { - if (peekVariableDeclarationStatement()) + // These two cases are very hard to distinguish: + // x[7 * 20 + 3] a; - x[7 * 20 + 3] = 9; + // In the first case, x is a type name, in the second it is the name of a variable. + switch (peekStatementType()) + { + case LookAheadInfo::VariableDeclarationStatement: return parseVariableDeclarationStatement(); - else + case LookAheadInfo::ExpressionStatement: return parseExpressionStatement(); + default: + break; + } + + // At this point, we have '(Identifier|ElementaryTypeName) "["'. + // We parse '(Identifier|ElementaryTypeName) ( "[" Expression "]" )+' and then decide whether to hand this over + // to ExpressionStatement or create a VariableDeclarationStatement out of it. + ASTPointer primary; + if (m_scanner->getCurrentToken() == Token::Identifier) + primary = parseIdentifier(); + else + { + primary = ASTNodeFactory(*this).createNode(m_scanner->getCurrentToken()); + m_scanner->next(); + } + vector, Location>> indices; + solAssert(m_scanner->getCurrentToken() == Token::LBrack, ""); + Location indexLocation = primary->getLocation(); + do + { + expectToken(Token::LBrack); + ASTPointer index; + if (m_scanner->getCurrentToken() != Token::RBrack) + index = parseExpression(); + indexLocation.end = getEndPosition(); + indices.push_back(make_pair(index, indexLocation)); + expectToken(Token::RBrack); + } + while (m_scanner->getCurrentToken() == Token::LBrack); + + if (m_scanner->getCurrentToken() == Token::Identifier) + return parseVariableDeclarationStatement(typeNameIndexAccessStructure(primary, indices)); + else + return parseExpressionStatement(expressionFromIndexAccessStructure(primary, indices)); } -ASTPointer Parser::parseVariableDeclarationStatement() +ASTPointer Parser::parseVariableDeclarationStatement( + ASTPointer const& _lookAheadArrayType) { - ASTNodeFactory nodeFactory(*this); VarDeclParserOptions options; options.allowVar = true; options.allowInitialValue = true; - ASTPointer variable = parseVariableDeclaration(options); + ASTPointer variable = parseVariableDeclaration(options, _lookAheadArrayType); + ASTNodeFactory nodeFactory(*this, variable); return nodeFactory.createNode(variable); } -ASTPointer Parser::parseExpressionStatement() +ASTPointer Parser::parseExpressionStatement( + ASTPointer const& _lookAheadIndexAccessStructure) { - ASTNodeFactory nodeFactory(*this); - ASTPointer expression = parseExpression(); - nodeFactory.setEndPositionFromNode(expression); - return nodeFactory.createNode(expression); + ASTPointer expression = parseExpression(_lookAheadIndexAccessStructure); + return ASTNodeFactory(*this, expression).createNode(expression); } -ASTPointer Parser::parseExpression() +ASTPointer Parser::parseExpression( + ASTPointer const& _lookAheadIndexAccessStructure) { - ASTNodeFactory nodeFactory(*this); - ASTPointer expression = parseBinaryExpression(); + ASTPointer expression = parseBinaryExpression(4, _lookAheadIndexAccessStructure); if (!Token::isAssignmentOp(m_scanner->getCurrentToken())) return expression; Token::Value assignmentOperator = expectAssignmentOperator(); ASTPointer rightHandSide = parseExpression(); + ASTNodeFactory nodeFactory(*this, expression); nodeFactory.setEndPositionFromNode(rightHandSide); return nodeFactory.createNode(expression, assignmentOperator, rightHandSide); } -ASTPointer Parser::parseBinaryExpression(int _minPrecedence) +ASTPointer Parser::parseBinaryExpression(int _minPrecedence, + ASTPointer const& _lookAheadIndexAccessStructure) { - ASTNodeFactory nodeFactory(*this); - ASTPointer expression = parseUnaryExpression(); + ASTPointer expression = parseUnaryExpression(_lookAheadIndexAccessStructure); + ASTNodeFactory nodeFactory(*this, expression); int precedence = Token::precedence(m_scanner->getCurrentToken()); for (; precedence >= _minPrecedence; --precedence) while (Token::precedence(m_scanner->getCurrentToken()) == precedence) @@ -653,11 +716,13 @@ ASTPointer Parser::parseBinaryExpression(int _minPrecedence) return expression; } -ASTPointer Parser::parseUnaryExpression() +ASTPointer Parser::parseUnaryExpression( + ASTPointer const& _lookAheadIndexAccessStructure) { - ASTNodeFactory nodeFactory(*this); + ASTNodeFactory nodeFactory = _lookAheadIndexAccessStructure ? + ASTNodeFactory(*this, _lookAheadIndexAccessStructure) : ASTNodeFactory(*this); Token::Value token = m_scanner->getCurrentToken(); - if (Token::isUnaryOp(token) || Token::isCountOp(token)) + if (!_lookAheadIndexAccessStructure && (Token::isUnaryOp(token) || Token::isCountOp(token))) { // prefix expression m_scanner->next(); @@ -668,7 +733,7 @@ ASTPointer Parser::parseUnaryExpression() else { // potential postfix expression - ASTPointer subExpression = parseLeftHandSideExpression(); + ASTPointer subExpression = parseLeftHandSideExpression(_lookAheadIndexAccessStructure); token = m_scanner->getCurrentToken(); if (!Token::isCountOp(token)) return subExpression; @@ -678,11 +743,16 @@ ASTPointer Parser::parseUnaryExpression() } } -ASTPointer Parser::parseLeftHandSideExpression() +ASTPointer Parser::parseLeftHandSideExpression( + ASTPointer const& _lookAheadIndexAccessStructure) { - ASTNodeFactory nodeFactory(*this); + ASTNodeFactory nodeFactory = _lookAheadIndexAccessStructure ? + ASTNodeFactory(*this, _lookAheadIndexAccessStructure) : ASTNodeFactory(*this); + ASTPointer expression; - if (m_scanner->getCurrentToken() == Token::New) + if (_lookAheadIndexAccessStructure) + expression = _lookAheadIndexAccessStructure; + else if (m_scanner->getCurrentToken() == Token::New) { expectToken(Token::New); ASTPointer contractName(parseIdentifier()); @@ -699,7 +769,9 @@ ASTPointer Parser::parseLeftHandSideExpression() case Token::LBrack: { m_scanner->next(); - ASTPointer index = parseExpression(); + ASTPointer index; + if (m_scanner->getCurrentToken() != Token::RBrack) + index = parseExpression(); nodeFactory.markEndPosition(); expectToken(Token::RBrack); expression = nodeFactory.createNode(expression, index); @@ -774,10 +846,7 @@ ASTPointer Parser::parsePrimaryExpression() m_scanner->next(); } else - { BOOST_THROW_EXCEPTION(createParserError("Expected primary expression.")); - return ASTPointer(); // this is not reached - } break; } return expression; @@ -824,18 +893,55 @@ pair>, vector>> Parser::pars return ret; } +Parser::LookAheadInfo Parser::peekStatementType() const +{ + // Distinguish between variable declaration (and potentially assignment) and expression statement + // (which include assignments to other expressions and pre-declared variables). + // We have a variable declaration if we get a keyword that specifies a type name. + // If it is an identifier or an elementary type name followed by an identifier, we also have + // a variable declaration. + // If we get an identifier followed by a "[", it can be both ("type[9] a;" or "arr[9] = 7;"). + // In all other cases, we have an expression statement. + Token::Value token(m_scanner->getCurrentToken()); + bool mightBeTypeName = (Token::isElementaryTypeName(token) || token == Token::Identifier); + if (token == Token::Mapping || token == Token::Var || + (mightBeTypeName && m_scanner->peekNextToken() == Token::Identifier)) + return LookAheadInfo::VariableDeclarationStatement; + if (mightBeTypeName && m_scanner->peekNextToken() == Token::LBrack) + return LookAheadInfo::IndexAccessStructure; + return LookAheadInfo::ExpressionStatement; +} + +ASTPointer Parser::typeNameIndexAccessStructure( + ASTPointer const& _primary, vector, Location>> const& _indices) +{ + ASTNodeFactory nodeFactory(*this, _primary); + ASTPointer type; + if (auto identifier = dynamic_cast(_primary.get())) + type = nodeFactory.createNode(make_shared(identifier->getName())); + else if (auto typeName = dynamic_cast(_primary.get())) + type = nodeFactory.createNode(typeName->getTypeToken()); + else + solAssert(false, "Invalid type name for array look-ahead."); + for (auto const& lengthExpression: _indices) + { + nodeFactory.setLocation(lengthExpression.second); + type = nodeFactory.createNode(type, lengthExpression.first); + } + return type; +} -bool Parser::peekVariableDeclarationStatement() +ASTPointer Parser::expressionFromIndexAccessStructure( + ASTPointer const& _primary, vector, Location>> const& _indices) { - // distinguish between variable declaration (and potentially assignment) and expression statement - // (which include assignments to other expressions and pre-declared variables) - // We have a variable declaration if we get a keyword that specifies a type name, or - // in the case of a user-defined type, we have two identifiers following each other. - return (m_scanner->getCurrentToken() == Token::Mapping || - m_scanner->getCurrentToken() == Token::Var || - ((Token::isElementaryTypeName(m_scanner->getCurrentToken()) || - m_scanner->getCurrentToken() == Token::Identifier) && - m_scanner->peekNextToken() == Token::Identifier)); + ASTNodeFactory nodeFactory(*this, _primary); + ASTPointer expression(_primary); + for (auto const& index: _indices) + { + nodeFactory.setLocation(index.second); + expression = nodeFactory.createNode(expression, index.first); + } + return expression; } void Parser::expectToken(Token::Value _value) diff --git a/libsolidity/Parser.h b/libsolidity/Parser.h index 4034aec85..9e4c7bb24 100644 --- a/libsolidity/Parser.h +++ b/libsolidity/Parser.h @@ -64,7 +64,9 @@ private: ASTPointer parseStructDefinition(); ASTPointer parseEnumDefinition(); ASTPointer parseEnumValue(); - ASTPointer parseVariableDeclaration(VarDeclParserOptions const& _options = VarDeclParserOptions()); + ASTPointer parseVariableDeclaration( + VarDeclParserOptions const& _options = VarDeclParserOptions(), + ASTPointer const& _lookAheadArrayType = ASTPointer()); ASTPointer parseModifierDefinition(); ASTPointer parseEventDefinition(); ASTPointer parseModifierInvocation(); @@ -77,13 +79,20 @@ private: ASTPointer parseIfStatement(); ASTPointer parseWhileStatement(); ASTPointer parseForStatement(); - ASTPointer parseVarDeclOrExprStmt(); - ASTPointer parseVariableDeclarationStatement(); - ASTPointer parseExpressionStatement(); - ASTPointer parseExpression(); - ASTPointer parseBinaryExpression(int _minPrecedence = 4); - ASTPointer parseUnaryExpression(); - ASTPointer parseLeftHandSideExpression(); + /// A "simple statement" can be a variable declaration statement or an expression statement. + ASTPointer parseSimpleStatement(); + ASTPointer parseVariableDeclarationStatement( + ASTPointer const& _lookAheadArrayType = ASTPointer()); + ASTPointer parseExpressionStatement( + ASTPointer const& _lookAheadIndexAccessStructure = ASTPointer()); + ASTPointer parseExpression( + ASTPointer const& _lookAheadIndexAccessStructure = ASTPointer()); + ASTPointer parseBinaryExpression(int _minPrecedence = 4, + ASTPointer const& _lookAheadIndexAccessStructure = ASTPointer()); + ASTPointer parseUnaryExpression( + ASTPointer const& _lookAheadIndexAccessStructure = ASTPointer()); + ASTPointer parseLeftHandSideExpression( + ASTPointer const& _lookAheadIndexAccessStructure = ASTPointer()); ASTPointer parsePrimaryExpression(); std::vector> parseFunctionCallListArguments(); std::pair>, std::vector>> parseFunctionCallArguments(); @@ -92,9 +101,24 @@ private: ///@{ ///@name Helper functions - /// Peeks ahead in the scanner to determine if a variable declaration statement is going to follow - bool peekVariableDeclarationStatement(); + /// Used as return value of @see peekStatementType. + enum class LookAheadInfo + { + IndexAccessStructure, VariableDeclarationStatement, ExpressionStatement + }; + /// Performs limited look-ahead to distinguish between variable declaration and expression statement. + /// For source code of the form "a[][8]" ("IndexAccessStructure"), this is not possible to + /// decide with constant look-ahead. + LookAheadInfo peekStatementType() const; + /// Returns a typename parsed in look-ahead fashion from something like "a[8][2**70]". + ASTPointer typeNameIndexAccessStructure( + ASTPointer const& _primary, + std::vector, Location>> const& _indices); + /// Returns an expression parsed in look-ahead fashion from something like "a[8][2**70]". + ASTPointer expressionFromIndexAccessStructure( + ASTPointer const& _primary, + std::vector, Location>> const& _indices); /// If current token value is not _value, throw exception otherwise advance token. void expectToken(Token::Value _value); Token::Value expectAssignmentOperator(); diff --git a/libsolidity/Types.cpp b/libsolidity/Types.cpp index ce51336df..c0be0d93e 100644 --- a/libsolidity/Types.cpp +++ b/libsolidity/Types.cpp @@ -58,7 +58,7 @@ TypePointer Type::fromElementaryTypeName(Token::Value _typeToken) else if (Token::String0 <= _typeToken && _typeToken <= Token::String32) return make_shared(int(_typeToken) - int(Token::String0)); else if (_typeToken == Token::Bytes) - return make_shared(ByteArrayType::Location::Storage); + return make_shared(ArrayType::Location::Storage); else BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unable to convert elementary typename " + std::string(Token::toString(_typeToken)) + " to type.")); @@ -83,17 +83,35 @@ TypePointer Type::fromUserDefinedTypeName(UserDefinedTypeName const& _typeName) return TypePointer(); } -TypePointer Type::fromMapping(Mapping const& _typeName) +TypePointer Type::fromMapping(ElementaryTypeName& _keyType, TypeName& _valueType) { - TypePointer keyType = _typeName.getKeyType().toType(); + TypePointer keyType = _keyType.toType(); if (!keyType) BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Error resolving type name.")); - TypePointer valueType = _typeName.getValueType().toType(); + TypePointer valueType = _valueType.toType(); if (!valueType) - BOOST_THROW_EXCEPTION(_typeName.getValueType().createTypeError("Invalid type name")); + BOOST_THROW_EXCEPTION(_valueType.createTypeError("Invalid type name.")); return make_shared(keyType, valueType); } +TypePointer Type::fromArrayTypeName(TypeName& _baseTypeName, Expression* _length) +{ + TypePointer baseType = _baseTypeName.toType(); + if (!baseType) + BOOST_THROW_EXCEPTION(_baseTypeName.createTypeError("Invalid type name.")); + if (_length) + { + if (!_length->getType()) + _length->checkTypeRequirements(); + auto const* length = dynamic_cast(_length->getType().get()); + if (!length) + BOOST_THROW_EXCEPTION(_length->createTypeError("Invalid array length.")); + return make_shared(ArrayType::Location::Storage, baseType, length->literalValue(nullptr)); + } + else + return make_shared(ArrayType::Location::Storage, baseType); +} + TypePointer Type::forLiteral(Literal const& _literal) { switch (_literal.getToken()) @@ -517,27 +535,27 @@ TypePointer ContractType::unaryOperatorResult(Token::Value _operator) const return _operator == Token::Delete ? make_shared() : TypePointer(); } -bool ByteArrayType::isImplicitlyConvertibleTo(const Type& _convertTo) const +bool ArrayType::isImplicitlyConvertibleTo(const Type& _convertTo) const { return _convertTo.getCategory() == getCategory(); } -TypePointer ByteArrayType::unaryOperatorResult(Token::Value _operator) const +TypePointer ArrayType::unaryOperatorResult(Token::Value _operator) const { if (_operator == Token::Delete) return make_shared(); return TypePointer(); } -bool ByteArrayType::operator==(Type const& _other) const +bool ArrayType::operator==(Type const& _other) const { if (_other.getCategory() != getCategory()) return false; - ByteArrayType const& other = dynamic_cast(_other); + ArrayType const& other = dynamic_cast(_other); return other.m_location == m_location; } -unsigned ByteArrayType::getSizeOnStack() const +unsigned ArrayType::getSizeOnStack() const { if (m_location == Location::CallData) // offset, length (stack top) @@ -547,12 +565,30 @@ unsigned ByteArrayType::getSizeOnStack() const return 1; } -shared_ptr ByteArrayType::copyForLocation(ByteArrayType::Location _location) const +string ArrayType::toString() const { - return make_shared(_location); + if (isByteArray()) + return "bytes"; + string ret = getBaseType()->toString() + "["; + if (!isDynamicallySized()) + ret += getLength().str(); + return ret + "]"; +} + +shared_ptr ArrayType::copyForLocation(ArrayType::Location _location) const +{ + auto copy = make_shared(_location); + copy->m_isByteArray = m_isByteArray; + if (m_baseType->getCategory() == Type::Category::Array) + copy->m_baseType = dynamic_cast(*m_baseType).copyForLocation(_location); + else + copy->m_baseType = m_baseType; + copy->m_hasDynamicLength = m_hasDynamicLength; + copy->m_length = m_length; + return copy; } -const MemberList ByteArrayType::s_byteArrayMemberList = MemberList({{"length", make_shared(256)}}); +const MemberList ArrayType::s_arrayTypeMemberList = MemberList({{"length", make_shared(256)}}); bool ContractType::operator==(Type const& _other) const { @@ -1040,7 +1076,7 @@ MagicType::MagicType(MagicType::Kind _kind): m_members = MemberList({{"sender", make_shared(0, IntegerType::Modifier::Address)}, {"gas", make_shared(256)}, {"value", make_shared(256)}, - {"data", make_shared(ByteArrayType::Location::CallData)}}); + {"data", make_shared(ArrayType::Location::CallData)}}); break; case Kind::Transaction: m_members = MemberList({{"origin", make_shared(0, IntegerType::Modifier::Address)}, diff --git a/libsolidity/Types.h b/libsolidity/Types.h index af64f1cb5..0d24b7221 100644 --- a/libsolidity/Types.h +++ b/libsolidity/Types.h @@ -36,8 +36,6 @@ namespace dev namespace solidity { -// @todo realMxN, dynamic strings, text, arrays - class Type; // forward class FunctionType; // forward using TypePointer = std::shared_ptr; @@ -78,7 +76,7 @@ class Type: private boost::noncopyable, public std::enable_shared_from_this[]) + * and dynamically-sized array ([]). */ -class ByteArrayType: public Type +class ArrayType: public Type { public: enum class Location { Storage, CallData, Memory }; - virtual Category getCategory() const override { return Category::ByteArray; } - explicit ByteArrayType(Location _location): m_location(_location) {} + virtual Category getCategory() const override { return Category::Array; } + + /// Constructor for a byte array ("bytes") + explicit ArrayType(Location _location): + m_location(_location), m_isByteArray(true), m_baseType(std::make_shared(8)) {} + /// Constructor for a dynamically sized array type ("type[]") + ArrayType(Location _location, const TypePointer &_baseType): + m_location(_location), m_baseType(_baseType) {} + /// Constructor for a fixed-size array type ("type[20]") + ArrayType(Location _location, const TypePointer &_baseType, u256 const& _length): + m_location(_location), m_baseType(_baseType), m_hasDynamicLength(false), m_length(_length) {} + virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; virtual bool operator==(const Type& _other) const override; - virtual bool isDynamicallySized() const { return true; } + virtual bool isDynamicallySized() const { return m_hasDynamicLength; } virtual unsigned getSizeOnStack() const override; - virtual std::string toString() const override { return "bytes"; } - virtual MemberList const& getMembers() const override { return s_byteArrayMemberList; } + virtual std::string toString() const override; + virtual MemberList const& getMembers() const override { return s_arrayTypeMemberList; } Location getLocation() const { return m_location; } + bool isByteArray() const { return m_isByteArray; } + TypePointer const& getBaseType() const { solAssert(!!m_baseType, ""); return m_baseType;} + u256 const& getLength() const { return m_length; } /// @returns a copy of this type with location changed to @a _location /// @todo this might move as far up as Type later - std::shared_ptr copyForLocation(Location _location) const; + std::shared_ptr copyForLocation(Location _location) const; private: Location m_location; - static const MemberList s_byteArrayMemberList; + bool m_isByteArray = false; ///< Byte arrays ("bytes") have different semantics from ordinary arrays. + TypePointer m_baseType; + bool m_hasDynamicLength = true; + u256 m_length; + static const MemberList s_arrayTypeMemberList; }; /** diff --git a/libsolidity/grammar.txt b/libsolidity/grammar.txt index a3b246873..6503516c6 100644 --- a/libsolidity/grammar.txt +++ b/libsolidity/grammar.txt @@ -18,8 +18,9 @@ ParameterList = '(' ( VariableDeclaration (',' VariableDeclaration)* )? ')' // semantic restriction: mappings and structs (recursively) containing mappings // are not allowed in argument lists VariableDeclaration = TypeName Identifier -TypeName = ElementaryTypeName | Identifier | Mapping +TypeName = ElementaryTypeName | Identifier | Mapping | ArrayTypeName Mapping = 'mapping' '(' ElementaryTypeName '=>' TypeName ')' +ArrayTypeName = TypeName '[' (Expression)? ']' Block = '{' Statement* '}' Statement = IfStatement | WhileStatement | Block | @@ -42,5 +43,5 @@ Assignment = Expression (AssignmentOp Expression) FunctionCall = Expression '(' Expression ( ',' Expression )* ')' NewExpression = 'new' Identifier MemberAccess = Expression '.' Identifier -IndexAccess = Expression '[' Expresison ']' +IndexAccess = Expression '[' (Expresison)? ']' PrimaryExpression = Identifier | NumberLiteral | StringLiteral | ElementaryTypeName | '(' Expression ')' diff --git a/mix/FileIo.cpp b/mix/FileIo.cpp index 818d8c887..506715767 100644 --- a/mix/FileIo.cpp +++ b/mix/FileIo.cpp @@ -1,18 +1,18 @@ /* - This file is part of cpp-ethereum. + This file is part of cpp-ethereum. - cpp-ethereum is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. - cpp-ethereum is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. - You should have received a copy of the GNU General Public License - along with cpp-ethereum. If not, see . + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . */ /** @file FileIo.cpp * @author Arkadiy Paronyan arkadiy@ethdev.com @@ -20,13 +20,21 @@ * Ethereum IDE client. */ +#include #include #include #include #include #include +#include +#include +#include +#include +#include #include "FileIo.h" +using namespace dev; +using namespace dev::crypto; using namespace dev::mix; void FileIo::makeDir(QString const& _url) @@ -101,3 +109,72 @@ bool FileIo::fileExists(QString const& _url) QFile file(url.path()); return file.exists(); } + +QStringList FileIo::makePackage(QString const& _deploymentFolder) +{ + + Json::Value manifest; + Json::Value entries(Json::arrayValue); + + QUrl folder(_deploymentFolder); + QString path(folder.path()); + QDir deployDir = QDir(path); + + dev::RLPStream rlpStr; + int k = 1; + std::vector files; + for (auto item: deployDir.entryInfoList(QDir::Files)) + { + QFile qFile(item.filePath()); + if (qFile.open(QIODevice::ReadOnly)) + { + k++; + QFileInfo fileInfo = QFileInfo(qFile.fileName()); + Json::Value jsonValue; + std::string path = fileInfo.fileName() == "index.html" ? "/" : fileInfo.fileName().toStdString(); + jsonValue["path"] = path; //TODO: Manage relative sub folder + jsonValue["file"] = "/" + fileInfo.fileName().toStdString(); + jsonValue["contentType"] = "text/html"; //TODO: manage multiple content type + QByteArray a = qFile.readAll(); + bytes data = bytes(a.begin(), a.end()); + files.push_back(data); + jsonValue["hash"] = toHex(dev::sha3(data).ref()); + entries.append(jsonValue); + } + qFile.close(); + } + rlpStr.appendList(k); + + std::stringstream jsonStr; + jsonStr << manifest; + QByteArray b = QString::fromStdString(jsonStr.str()).toUtf8(); + rlpStr.append(bytesConstRef((const unsigned char*)b.data(), b.size())); + + for (unsigned int k = 0; k < files.size(); k++) + rlpStr.append(files.at(k)); + + manifest["entries"] = entries; + bytes dapp = rlpStr.out(); + dev::h256 dappHash = dev::sha3(dapp); + //encrypt + KeyPair key(dappHash); + Secp256k1 enc; + enc.encrypt(key.pub(), dapp); + + QUrl url(_deploymentFolder + "package.dapp"); + QFile compressed(url.path()); + QByteArray qFileBytes((char*)dapp.data(), dapp.size()); + if (compressed.open(QIODevice::WriteOnly)) + { + compressed.write(qFileBytes); + compressed.flush(); + } + else + error(tr("Error creating package.dapp")); + compressed.close(); + QStringList ret; + ret.append(QString::fromStdString(toHex(dappHash.ref()))); + ret.append(qFileBytes.toBase64()); + return ret; +} + diff --git a/mix/FileIo.h b/mix/FileIo.h index 08d49e099..3646627b8 100644 --- a/mix/FileIo.h +++ b/mix/FileIo.h @@ -1,18 +1,18 @@ /* - This file is part of cpp-ethereum. + This file is part of cpp-ethereum. - cpp-ethereum is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. - cpp-ethereum is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. - You should have received a copy of the GNU General Public License - along with cpp-ethereum. If not, see . + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . */ /** @file FileIo.h * @author Arkadiy Paronyan arkadiy@ethdev.com @@ -22,6 +22,7 @@ #pragma once +#include #include namespace dev @@ -52,6 +53,8 @@ public: Q_INVOKABLE void moveFile(QString const& _sourceUrl, QString const& _destUrl); /// Check if file exists Q_INVOKABLE bool fileExists(QString const& _url); + /// Compress a folder, @returns sha3 of the compressed file. + Q_INVOKABLE QStringList makePackage(QString const& _deploymentFolder); private: QString getHomePath() const; diff --git a/mix/QVariableDefinition.h b/mix/QVariableDefinition.h index 1825c2567..8d890539b 100644 --- a/mix/QVariableDefinition.h +++ b/mix/QVariableDefinition.h @@ -53,6 +53,8 @@ public: virtual bytes encodeValue() = 0; /// Decode the return value @a _rawValue. virtual void decodeValue(dev::bytes const& _rawValue) = 0; + /// returns String representation of the encoded value. + Q_INVOKABLE QString encodeValueAsString() { return QString::fromStdString(dev::toHex(encodeValue())); } protected: QString m_value; diff --git a/mix/qml/DeploymentDialog.qml b/mix/qml/DeploymentDialog.qml new file mode 100644 index 000000000..5033d02b8 --- /dev/null +++ b/mix/qml/DeploymentDialog.qml @@ -0,0 +1,232 @@ +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.Controls.Styles 1.3 +import org.ethereum.qml.QEther 1.0 +import "js/TransactionHelper.js" as TransactionHelper +import "js/ProjectModel.js" as ProjectModelCode +import "js/QEtherHelper.js" as QEtherHelper +import "." + + +Window { + id: modalDeploymentDialog + modality: Qt.ApplicationModal + width: 600 + height: 350 + visible: false + property alias applicationUrlEth: applicationUrlEth.text + property alias applicationUrlHttp: applicationUrlHttp.text + property string urlHintContract: "c83d3e22645fb015d02043a744921cc2f828c64d" + property string packageHash + property alias packageBase64: base64Value.text + property string eth: "afb7cdbd076674fd2c67f8a66518e3145b184ae4"; + property string wallet: "c83d3e22645fb015d02043a744921cc2f828c64d"; + + color: Style.generic.layout.backgroundColor + + function close() + { + visible = false; + } + + function open() + { + modalDeploymentDialog.setX((Screen.width - width) / 2); + modalDeploymentDialog.setY((Screen.height - height) / 2); + visible = true; + } + + function pad(h) + { + // TODO move this to QHashType class + while (h.length < 64) + { + h = '0' + h; + } + return h; + } + + Rectangle + { + anchors.fill : parent + anchors.margins: 10 + color: Style.generic.layout.backgroundColor + GridLayout + { + columns: 2 + anchors.top: parent.top + anchors.left: parent.left + width: parent.width + DefaultLabel + { + text: qsTr("Ethereum Application URL: ") + } + + DefaultTextField + { + Layout.fillWidth: true + id: applicationUrlEth + } + + DefaultLabel + { + text: qsTr("Web Application Ressources URL: ") + } + + DefaultTextField + { + Layout.fillWidth: true + id: applicationUrlHttp + } + + DefaultLabel + { + text: qsTr("Package (Base64): ") + } + + TextArea + { + Layout.fillWidth: true + readOnly: true + id: base64Value + height: 60 + enabled: base64Value.text != "" + } + } + + MessageDialog { + id: deployDialog + standardButtons: StandardButton.Ok + icon: StandardIcon.Warning + } + + RowLayout + { + anchors.bottom: parent.bottom + anchors.right: parent.right; + anchors.bottomMargin: 10 + Button { + text: qsTr("Deploy to Ethereum"); + tooltip: qsTr("Deploy contract and package resources files.") + onClicked: { + deployWarningDialog.open(); + } + } + + Button { + text: qsTr("Register Web Application"); + tooltip: qsTr("Register hosted Web Application.") + onClicked: { + 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(); + } + else + ProjectModelCode.registerToUrlHint(); + } + } + + Button { + text: qsTr("Close"); + onClicked: close(); + } + + Button { + text: qsTr("Check Ownership"); + visible : false + onClicked: { + var requests = []; + var ethStr = QEtherHelper.createString("wallet"); + + var ethHash = QEtherHelper.createHash(eth); + + requests.push({ //owner + jsonrpc: "2.0", + method: "eth_call", + params: [ { "to": '0x' + modalDeploymentDialog.eth, "data": "0xec7b9200" + ethStr.encodeValueAsString() } ], + id: 3 + }); + + requests.push({ //register + jsonrpc: "2.0", + method: "eth_call", + params: [ { "to": '0x' + modalDeploymentDialog.eth, "data": "0x6be16bed" + ethStr.encodeValueAsString() } ], + id: 4 + }); + + var jsonRpcUrl = "http://localhost:8080"; + var rpcRequest = JSON.stringify(requests); + var httpRequest = new XMLHttpRequest(); + httpRequest.open("POST", jsonRpcUrl, true); + httpRequest.setRequestHeader("Content-type", "application/json"); + httpRequest.setRequestHeader("Content-length", rpcRequest.length); + httpRequest.setRequestHeader("Connection", "close"); + httpRequest.onreadystatechange = function() { + if (httpRequest.readyState === XMLHttpRequest.DONE) { + if (httpRequest.status === 200) { + console.log(httpRequest.responseText); + } else { + var errorText = qsTr("path registration failed ") + httpRequest.status; + console.log(errorText); + } + } + } + httpRequest.send(rpcRequest); + } + } + + + Button { + text: qsTr("Generate registrar init"); + visible: false + onClicked: { + console.log("registering eth/wallet") + var jsonRpcRequestId = 0; + + var requests = []; + + var walletStr = QEtherHelper.createString("wallet"); + + requests.push({ //reserve + jsonrpc: "2.0", + method: "eth_transact", + params: [ { "to": '0x' + modalDeploymentDialog.eth, "data": "0x1c83171b" + walletStr.encodeValueAsString() } ], + id: jsonRpcRequestId++ + }); + + + requests.push({ //setRegister + jsonrpc: "2.0", + method: "eth_transact", + params: [ { "to": '0x' + modalDeploymentDialog.eth, "data": "0x96077307" + walletStr.encodeValueAsString() + pad(wallet) } ], + id: jsonRpcRequestId++ + }); + + var jsonRpcUrl = "http://localhost:8080"; + var rpcRequest = JSON.stringify(requests); + var httpRequest = new XMLHttpRequest(); + httpRequest.open("POST", jsonRpcUrl, true); + httpRequest.setRequestHeader("Content-type", "application/json"); + httpRequest.setRequestHeader("Content-length", rpcRequest.length); + httpRequest.setRequestHeader("Connection", "close"); + httpRequest.onreadystatechange = function() { + if (httpRequest.readyState === XMLHttpRequest.DONE) { + if (httpRequest.status === 200) { + console.log(httpRequest.responseText); + } else { + var errorText = qsTr("path registration failed ") + httpRequest.status; + console.log(errorText); + } + } + } + httpRequest.send(rpcRequest); + } + } + } + } +} diff --git a/mix/qml/ProjectModel.qml b/mix/qml/ProjectModel.qml index 97dec227c..19008be5e 100644 --- a/mix/qml/ProjectModel.qml +++ b/mix/qml/ProjectModel.qml @@ -21,6 +21,7 @@ Item { signal newProject(var projectData) signal documentSaved(var documentId) signal deploymentStarted() + signal deploymentStepChanged(string message) signal deploymentComplete() signal deploymentError(string error) @@ -31,7 +32,7 @@ Item { property string projectPath: "" property string projectTitle: "" property string currentDocumentId: "" - property string deploymentAddress: "" + property var deploymentAddresses: [] property var listModel: projectListModel property var stateListModel: projectStateListModel.model property CodeEditorView codeEditor: null @@ -55,6 +56,7 @@ Item { function getDocumentIndex(documentId) { return ProjectModelCode.getDocumentIndex(documentId); } function addExistingFiles(paths) { ProjectModelCode.doAddExistingFiles(paths); } function deployProject() { ProjectModelCode.deployProject(false); } + function registerToUrlHint() { ProjectModelCode.registerToUrlHint(); } Connections { target: appContext @@ -91,15 +93,44 @@ Item { MessageDialog { id: deployWarningDialog + property bool redeploy title: qsTr("Project") - text: qsTr("This project has been already deployed to the network. Do you want to re-deploy it?") - standardButtons: StandardButton.Ok | StandardButton.Cancel + text: + { + if (Object.keys(projectModel.deploymentAddresses).length > 0) + { + redeploy = true + standardButtons = StandardButton.Ok | StandardButton.Reset | StandardButton.Abort; + return qsTr("This project has been already deployed to the network. Do you want to repackage the resources only, or also reset the deployed contract to its initial state?") + } + else + { + redeploy = false; + standardButtons = StandardButton.Ok | StandardButton.Abort; + return qsTr("This action will deploy to the network. Do you want to deploy it?") + } + } icon: StandardIcon.Question onAccepted: { - ProjectModelCode.deployProject(true); + ProjectModelCode.startDeployProject(!redeploy); + } + onReset: { + ProjectModelCode.startDeployProject(true); } } + MessageDialog { + id: deployRessourcesDialog + title: qsTr("Project") + standardButtons: StandardButton.Ok + icon: StandardIcon.Info + } + + DeploymentDialog + { + id: deploymentDialog + } + ListModel { id: projectListModel } diff --git a/mix/qml/StatusPane.qml b/mix/qml/StatusPane.qml index 6a8a0093d..1f226279d 100644 --- a/mix/qml/StatusPane.qml +++ b/mix/qml/StatusPane.qml @@ -47,6 +47,7 @@ Rectangle { onDeploymentStarted: infoMessage(qsTr("Running deployment...")); onDeploymentError: infoMessage(error); onDeploymentComplete: infoMessage(qsTr("Deployment complete")); + onDeploymentStepChanged: infoMessage(message); } Connections { target: codeModel diff --git a/mix/qml/Style.qml b/mix/qml/Style.qml index 9e4b6f268..c317177a3 100644 --- a/mix/qml/Style.qml +++ b/mix/qml/Style.qml @@ -11,6 +11,7 @@ QtObject { property QtObject generic: QtObject { property QtObject layout: QtObject { property string separatorColor: "#808080" + property string backgroundColor: "#ededed" } property QtObject size: QtObject { property string titlePointSize: absoluteSize(0) diff --git a/mix/qml/TransactionDialog.qml b/mix/qml/TransactionDialog.qml index e5a8bc746..434f8a850 100644 --- a/mix/qml/TransactionDialog.qml +++ b/mix/qml/TransactionDialog.qml @@ -405,7 +405,7 @@ Window { return boolViewComp; else if (type.indexOf("string") !== -1) return stringViewComp; - else if (type.indexOf("hash") !== -1) + else if (type.indexOf("hash") !== -1 || type.indexOf("address") !== -1) return hashViewComp; else return null; diff --git a/mix/qml/js/ProjectModel.js b/mix/qml/js/ProjectModel.js index 9ca9bae39..d60dc4124 100644 --- a/mix/qml/js/ProjectModel.js +++ b/mix/qml/js/ProjectModel.js @@ -19,6 +19,7 @@ * @date 2015 * Ethereum IDE client. */ +Qt.include("QEtherHelper.js") var htmlTemplate = "\n\n\n\n\n\n\n"; var contractTemplate = "contract Contract {\n}\n"; @@ -45,7 +46,11 @@ function saveProject() { var projectData = { files: [], title: projectTitle, - deploymentAddress: deploymentAddress + deploymentAddresses: deploymentAddresses, + applicationUrlEth: deploymentDialog.applicationUrlEth, + applicationUrlHttp: deploymentDialog.applicationUrlHttp, + packageHash: deploymentDialog.packageHash, + packageBase64: deploymentDialog.packageBase64 }; for (var i = 0; i < projectListModel.count; i++) projectData.files.push(projectListModel.get(i).fileName) @@ -63,11 +68,19 @@ function loadProject(path) { var projectFile = path + projectFileName; var json = fileIo.readFile(projectFile); var projectData = JSON.parse(json); + if (projectData.packageHash) + deploymentDialog.packageHash = projectData.packageHash + if (projectData.packageBase64) + deploymentDialog.packageBase64 = projectData.packageBase64 + if (projectData.applicationUrlEth) + deploymentDialog.applicationUrlEth = projectData.applicationUrlEth + if (projectData.applicationUrlHttp) + deploymentDialog.applicationUrlHttp = projectData.applicationUrlHttp if (!projectData.title) { var parts = path.split("/"); projectData.title = parts[parts.length - 2]; } - deploymentAddress = projectData.deploymentAddress ? projectData.deploymentAddress : ""; + deploymentAddresses = projectData.deploymentAddresses ? projectData.deploymentAddresses : []; projectTitle = projectData.title; projectPath = path; if (!projectData.files) @@ -272,22 +285,27 @@ function generateFileName(name, extension) { var jsonRpcRequestId = 1; function deployProject(force) { - saveAll(); //TODO: ask user + deploymentDialog.open(); +} - if (!force && deploymentAddress !== "") { - deployWarningDialog.visible = true; +function startDeployProject(erasePrevious) +{ + var date = new Date(); + var deploymentId = date.toLocaleString(Qt.locale(), "ddMMyyHHmmsszzz"); + if (!erasePrevious) + { + finalizeDeployment(deploymentId, projectModel.deploymentAddresses); return; } - var date = new Date(); - var deploymentId = date.toLocaleString(Qt.locale(), "ddMMyyHHmmsszzz"); - var jsonRpcUrl = "http://127.0.0.1:8080"; + var jsonRpcUrl = "http://127.0.0.1:8080"; console.log("Deploying " + deploymentId + " to " + jsonRpcUrl); deploymentStarted(); var requests = []; var requestNames = []; + for (var c in codeModel.contracts) { //TODO: order based on dependencies var code = codeModel.contracts[c].codeHex; requests.push({ @@ -299,8 +317,8 @@ function deployProject(force) { requestNames.push(c); } - var rpcRequest = JSON.stringify(requests);; - var httpRequest = new XMLHttpRequest() + var rpcRequest = JSON.stringify(requests); + var httpRequest = new XMLHttpRequest(); httpRequest.open("POST", jsonRpcUrl, true); httpRequest.setRequestHeader("Content-type", "application/json"); httpRequest.setRequestHeader("Content-length", rpcRequest.length); @@ -311,8 +329,8 @@ function deployProject(force) { var rpcResponse = JSON.parse(httpRequest.responseText); if (rpcResponse.length === requestNames.length) { var contractAddresses = {}; - for (var r = 0; r < rpcResponse.lenght; r++) - contractAddresses[requestNames[r]] = rpcResponse.result; + for (var r = 0; r < rpcResponse.length; r++) + contractAddresses[requestNames[r]] = rpcResponse[r].result; finalizeDeployment(deploymentId, contractAddresses); } } else { @@ -326,7 +344,7 @@ function deployProject(force) { } function finalizeDeployment(deploymentId, addresses) { - //create a dir for frontend files and copy them + deploymentStepChanged(qsTr("Packaging application ...")); var deploymentDir = projectPath + deploymentId + "/"; fileIo.makeDir(deploymentDir); for (var i = 0; i < projectListModel.count; i++) { @@ -369,7 +387,203 @@ function finalizeDeployment(deploymentId, addresses) { //copy scripts fileIo.copyFile("qrc:///js/bignumber.min.js", deploymentDir + "bignumber.min.js"); fileIo.copyFile("qrc:///js/webthree.js", deploymentDir + "ethereum.js"); - deploymentAddress = address; + deploymentAddresses = addresses; saveProject(); - deploymentComplete(); + + var packageRet = fileIo.makePackage(deploymentDir); + deploymentDialog.packageHash = packageRet[0]; + deploymentDialog.packageBase64 = packageRet[1]; + + var applicationUrlEth = deploymentDialog.applicationUrlEth; + applicationUrlEth = formatAppUrl(applicationUrlEth); + + deploymentStepChanged(qsTr("Registering application on the Ethereum network ...")); + checkRegistration(applicationUrlEth, deploymentDialog.eth, function () { + deploymentComplete(); + deployRessourcesDialog.text = qsTr("Register Web Application to finalize deployment."); + deployRessourcesDialog.open(); + }); +} + +function rpcCall(requests, callBack) +{ + var jsonRpcUrl = "http://localhost:8080"; + var rpcRequest = JSON.stringify(requests); + var httpRequest = new XMLHttpRequest(); + httpRequest.open("POST", jsonRpcUrl, true); + httpRequest.setRequestHeader("Content-type", "application/json"); + httpRequest.setRequestHeader("Content-length", rpcRequest.length); + httpRequest.setRequestHeader("Connection", "close"); + httpRequest.onreadystatechange = function() { + if (httpRequest.readyState === XMLHttpRequest.DONE) { + if (httpRequest.status !== 200) + { + var errorText = qsTr("Deployment error: Error while registering Dapp ") + httpRequest.status; + console.log(errorText); + deploymentError(errorText); + return; + } + callBack(httpRequest.status, httpRequest.responseText) + } + } + httpRequest.send(rpcRequest); +} + + +function checkRegistration(dappUrl, addr, callBack) +{ + var requests = []; + var data = ""; + if (dappUrl.length > 0) + { + //checking path (register). + var str = createString(dappUrl[0]); + data = "0x6be16bed" + str.encodeValueAsString(); + console.log("checking if path exists (register) => " + JSON.stringify(dappUrl)); + requests.push({ + jsonrpc: "2.0", + method: "eth_call", + params: [ { "to": '0x' + addr, "data": data } ], + id: jsonRpcRequestId++ + }); + + rpcCall(requests, function (httpRequest, response) { + var address = JSON.parse(response)[0].result.replace('0x', ''); + if (address === "") + { + var errorTxt = qsTr("Path does not exists " + JSON.stringify(dappUrl) + " cannot continue"); + deploymentError(errorTxt); + console.log(errorTxt); + return; + } + + dappUrl.splice(0, 1); + checkRegistration(dappUrl, address, callBack); + }); + } + else + { + var paramTitle = createString(projectModel.projectTitle); + requests.push({ + //owner() + jsonrpc: "2.0", + method: "eth_call", + params: [ { "to": '0x' + addr, "data": "0xec7b9200" + paramTitle.encodeValueAsString() } ], + id: jsonRpcRequestId++ + }); + + requests.push({ + //accounts + jsonrpc: "2.0", + method: "eth_accounts", + params: null, + id: jsonRpcRequestId++ + }); + + rpcCall(requests, function (httpRequest, response) { + requests = []; + var res = JSON.parse(response); + var currentOwner = res[0].result; + var noOwner = currentOwner.replace('0x', '').replace(/0/g, '') === ''; + + if (noOwner) + { + requests.push({ + //reserve() + jsonrpc: "2.0", + method: "eth_transact", + params: [ { "to": '0x' + addr, "data": "0x1c83171b" + paramTitle.encodeValueAsString() } ], + id: jsonRpcRequestId++ + }); + } + else + { + var bOwner = false; + currentOwner = normalizeAddress(currentOwner); + for (var u in res[1].result) + { + if (normalizeAddress(res[1].result[u]) === currentOwner) + bOwner = true; + } + + if (!bOwner) + { + var errorTxt = qsTr("Current user is not the owner of this path. Cannot continue") + deploymentError(errorTxt); + console.log(errorTxt); + return; + } + } + console.log("setContentHash"); + requests.push({ + //setContent() + jsonrpc: "2.0", + method: "eth_transact", + params: [ { "to": '0x' + addr, "data": "0x5d574e32" + paramTitle.encodeValueAsString() + deploymentDialog.packageHash } ], + id: jsonRpcRequestId++ + }); + rpcCall(requests, function (httpRequest, response) { + callBack(); + }); + }); + } } + +function registerToUrlHint() +{ + deploymentStepChanged(qsTr("Registering application Resources (" + deploymentDialog.applicationUrlHttp) + ") ..."); + var requests = []; + var paramUrlHttp = createString(deploymentDialog.applicationUrlHttp); + requests.push({ + //urlHint => suggestUrl + jsonrpc: "2.0", + method: "eth_transact", + params: [ { "to": '0x' + deploymentDialog.urlHintContract, "data": "0x4983e19c" + deploymentDialog.packageHash + paramUrlHttp.encodeValueAsString() } ], + id: jsonRpcRequestId++ + }); + + rpcCall(requests, function (httpRequest, response) { + deploymentComplete(); + }); +} + +function normalizeAddress(addr) +{ + addr = addr.replace('0x', ''); + var i = 0; + for (var k in addr) + { + if (addr[k] !== "0") + break; + else + i++; + } + return addr.substring(i); +} + +function formatAppUrl(url) +{ + var slash = url.indexOf("/"); + var dot = url.indexOf("."); + if (slash === -1 && dot === -1) + return url; + if ((slash !== -1 && slash < dot) || dot === -1) + return url.split("/"); + else + { + var dotted; + var ret = []; + if (slash !== -1) + { + ret.push(url.split("/")); + dotted = ret[0].split("."); + } + else + dotted = url.split("."); + + for (var k in dotted) + ret.unshift(dotted[k]); + return ret; + } +} + diff --git a/mix/qml/js/QEtherHelper.js b/mix/qml/js/QEtherHelper.js index 7563941d2..71ee258c3 100644 --- a/mix/qml/js/QEtherHelper.js +++ b/mix/qml/js/QEtherHelper.js @@ -15,3 +15,19 @@ function createBigInt(_value) return bigint; } +function createString(_value) +{ + var stringComponent = Qt.createComponent("qrc:/qml/QStringType.qml"); + var stringC = stringComponent.createObject(); + stringC.setValue(_value); + return stringC; +} + +function createHash(_value) +{ + var hComponent = Qt.createComponent("qrc:/qml/QHashType.qml"); + var hC = hComponent.createObject(); + hC.setValue(_value); + return hC; +} + diff --git a/mix/res.qrc b/mix/res.qrc index 20e51a9b3..b2a6385a1 100644 --- a/mix/res.qrc +++ b/mix/res.qrc @@ -101,5 +101,6 @@ qml/Style.qml qml/WebPreviewStyle.qml qml/img/available_updates.png + qml/DeploymentDialog.qml diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index bc14918b8..7ddfdb40d 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -4,6 +4,10 @@ aux_source_directory(. SRC_LIST) list(REMOVE_ITEM SRC_LIST "./createRandomTest.cpp") list(REMOVE_ITEM SRC_LIST "./checkRandomTest.cpp") +if (NOT JSONRPC) + list(REMOVE_ITEM SRC_LIST "./AccountHolder.cpp") +endif() + include_directories(BEFORE ${JSONCPP_INCLUDE_DIRS}) include_directories(BEFORE ..) include_directories(${Boost_INCLUDE_DIRS}) diff --git a/test/SolidityNameAndTypeResolution.cpp b/test/SolidityNameAndTypeResolution.cpp index df970a6ed..4809aac1e 100644 --- a/test/SolidityNameAndTypeResolution.cpp +++ b/test/SolidityNameAndTypeResolution.cpp @@ -1176,6 +1176,15 @@ BOOST_AUTO_TEST_CASE(test_for_bug_override_function_with_bytearray_type) BOOST_CHECK_NO_THROW(parseTextAndResolveNamesWithChecks(sourceCode)); } +BOOST_AUTO_TEST_CASE(array_with_nonconstant_length) +{ + char const* text = R"( + contract c { + function f(uint a) { uint8[a] x; } + })"; + BOOST_CHECK_THROW(parseTextAndResolveNames(text), TypeError); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/SolidityParser.cpp b/test/SolidityParser.cpp index 69bbc6e0a..75eba8bc6 100644 --- a/test/SolidityParser.cpp +++ b/test/SolidityParser.cpp @@ -480,6 +480,18 @@ BOOST_AUTO_TEST_CASE(statement_starting_with_type_conversion) char const* text = "contract test {\n" " function fun() {\n" " uint64(2);\n" + " uint64[7](3);\n" + " uint64[](3);\n" + " }\n" + "}\n"; + BOOST_CHECK_NO_THROW(parseText(text)); +} + +BOOST_AUTO_TEST_CASE(type_conversion_to_dynamic_array) +{ + char const* text = "contract test {\n" + " function fun() {\n" + " var x = uint64[](3);\n" " }\n" "}\n"; BOOST_CHECK_NO_THROW(parseText(text)); @@ -753,6 +765,45 @@ BOOST_AUTO_TEST_CASE(external_variable) BOOST_CHECK_THROW(parseText(text), ParserError); } +BOOST_AUTO_TEST_CASE(arrays_in_storage) +{ + char const* text = R"( + contract c { + uint[10] a; + uint[] a2; + struct x { uint[2**20] b; y[0] c; } + struct y { uint d; mapping(uint=>x)[] e; } + })"; + BOOST_CHECK_NO_THROW(parseText(text)); +} + +BOOST_AUTO_TEST_CASE(arrays_in_events) +{ + char const* text = R"( + contract c { + event e(uint[10] a, string7[8] indexed b, c[3] x); + })"; + BOOST_CHECK_NO_THROW(parseText(text)); +} + +BOOST_AUTO_TEST_CASE(arrays_in_expressions) +{ + char const* text = R"( + contract c { + function f() { c[10] a = 7; uint8[10 * 2] x; } + })"; + BOOST_CHECK_NO_THROW(parseText(text)); +} + +BOOST_AUTO_TEST_CASE(multi_arrays) +{ + char const* text = R"( + contract c { + mapping(uint => mapping(uint => int8)[8][][9])[] x; + })"; + BOOST_CHECK_NO_THROW(parseText(text)); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/stSolidityTestFiller.json b/test/stSolidityTestFiller.json index 973c52a8d..17c40ed26 100644 --- a/test/stSolidityTestFiller.json +++ b/test/stSolidityTestFiller.json @@ -264,7 +264,7 @@ }, "pre" : { - "a94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { + "095e7baea6a6c7c4c2dfeb977efac326af552d87" : { "balance" : "100000", "//": "contract subcaller ", "//": "{ ", @@ -294,6 +294,12 @@ "nonce" : "0", "storage" : { } + }, + "a94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { + "balance" : "100000", + "nonce" : "0", + "code" : "", + "storage": {} } }, "transaction" : @@ -304,7 +310,7 @@ "gasPrice" : "1", "nonce" : "0", "secretKey" : "45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8", - "to" : "a94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "to" : "095e7baea6a6c7c4c2dfeb977efac326af552d87", "value" : "1" } }, @@ -320,7 +326,7 @@ }, "pre" : { - "a94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { + "095e7baea6a6c7c4c2dfeb977efac326af552d87" : { "balance" : "100000", "//" : "contract recursiveMethods ", "//" : "{ ", @@ -343,6 +349,12 @@ "nonce" : "0", "storage" : { } + }, + "a94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { + "balance" : "100000", + "nonce" : "0", + "code" : "", + "storage": {} } }, "transaction" : @@ -353,7 +365,7 @@ "gasPrice" : "1", "nonce" : "0", "secretKey" : "45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8", - "to" : "a94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "to" : "095e7baea6a6c7c4c2dfeb977efac326af552d87", "value" : "1" } }, @@ -369,7 +381,7 @@ }, "pre" : { - "a94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { + "095e7baea6a6c7c4c2dfeb977efac326af552d87" : { "balance" : "100000", "//" : "contract recursiveMethods ", "//" : "{ ", @@ -392,6 +404,12 @@ "nonce" : "0", "storage" : { } + }, + "a94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { + "balance" : "500", + "nonce" : "0", + "code" : "", + "storage": {} } }, "transaction" : @@ -402,7 +420,7 @@ "gasPrice" : "1", "nonce" : "0", "secretKey" : "45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8", - "to" : "a94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "to" : "095e7baea6a6c7c4c2dfeb977efac326af552d87", "value" : "1" } }, @@ -418,7 +436,7 @@ }, "pre" : { - "a94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { + "095e7baea6a6c7c4c2dfeb977efac326af552d87" : { "balance" : "1000000", "//" : "contract recursiveCreate1 ", "//" : "{ ", @@ -461,6 +479,12 @@ "nonce" : "0", "storage" : { } + }, + "a94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { + "balance" : "500000", + "nonce" : "0", + "code" : "", + "storage": {} } }, "transaction" : @@ -471,7 +495,7 @@ "gasPrice" : "1", "nonce" : "0", "secretKey" : "45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8", - "to" : "a94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "to" : "095e7baea6a6c7c4c2dfeb977efac326af552d87", "value" : "1" } }, @@ -487,12 +511,18 @@ }, "pre" : { - "a94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { + "095e7baea6a6c7c4c2dfeb977efac326af552d87" : { "balance" : "100000", "code" : "0x60003560e060020a90048063c040622614601557005b601b6021565b60006000f35b61014f60008190555056", "nonce" : "0", "storage" : { } + }, + "a94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { + "balance" : "500", + "nonce" : "0", + "code" : "", + "storage": {} } }, "transaction" : @@ -503,7 +533,7 @@ "gasPrice" : "1", "nonce" : "0", "secretKey" : "45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8", - "to" : "a94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "to" : "095e7baea6a6c7c4c2dfeb977efac326af552d87", "value" : "1" } } diff --git a/test/stTransactionTestFiller.json b/test/stTransactionTestFiller.json index 73026608d..37b752f86 100644 --- a/test/stTransactionTestFiller.json +++ b/test/stTransactionTestFiller.json @@ -842,78 +842,6 @@ } }, - "TransactionMakeAccountBalanceOverflow" : { - "env" : { - "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", - "currentDifficulty" : "45678256", - "currentGasLimit" : "1000000", - "currentNumber" : "0", - "currentTimestamp" : 1, - "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6" - }, - "pre" : - { - "a94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { - "balance" : "100000", - "code" : "", - "nonce" : "0", - "storage" : { - } - }, - - "b94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { - "balance" : "115792089237316195423570985008687907853269984665640564039457584007913129639935", - "code" : "", - "nonce" : "0", - "storage" : { - } - } - }, - "transaction" : - { - "data" : "", - "gasLimit" : "1000", - "gasPrice" : "1", - "nonce" : "0", - "secretKey" : "45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8", - "to" : "b94f5374fce5edbc8e2a8697c15331677e6ebf0b", - "value" : "100" - } - }, - - "TransactionMakeAccountNonceOverflow" : { - "env" : { - "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", - "currentDifficulty" : "1", - "currentGasLimit" : "1000000", - "currentNumber" : "0", - "currentTimestamp" : 1, - "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6" - }, - "pre" : - { - "a94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { - "balance" : "100000", - "code" : "", - "nonce" : "115792089237316195423570985008687907853269984665640564039457584007913129639935", - "nonce" : "10000", - "storage" : { - } - } - }, - "transaction" : - { - "data" : "", - "gasLimit" : "1000", - "gasPrice" : "1", - "nonce" : "115792089237316195423570985008687907853269984665640564039457584007913129639935", - "nonce" : "10000", - "secretKey" : "45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8", - "to" : "b94f5374fce5edbc8e2a8697c15331677e6ebf0b", - "value" : "100" - } - }, - "UserTransactionZeroCost" : { "env" : { "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", diff --git a/test/ttTransactionTestFiller.json b/test/ttTransactionTestFiller.json index 78615bb45..c54da4db6 100644 --- a/test/ttTransactionTestFiller.json +++ b/test/ttTransactionTestFiller.json @@ -294,7 +294,7 @@ "gasPrice" : "1", "nonce" : "0xffdc5", "to" : "b94f5374fce5edbc8e2a8697c15331677e6ebf0b", - "value" : "0xfffdc12c", + "value" : "4294820140", "v" : "28", "r" : "0x98ff921201554726367d2be8c804a7ff89ccf285ebc57dff8ae4c44b9c19ac4a", "s" : "0x8887321be575c8095f789dd4c743dfe42c1820f9231f98a962b210e3ac2452a3" @@ -314,5 +314,18 @@ "r" : "0x98ff921201554726367d2be8c804a7ff89ccf285ebc57dff8ae4c44b9c19ac4a", "s" : "0x8887321be575c8095f789dd4c743dfe42c1820f9231f98a962b210e3ac2452a3" } + }, + "unpadedRValue": { + "transaction": { + "nonce": "13", + "gasPrice": "0x09184e72a000", + "gasLimit": "0x2710", + "to": "7c47ef93268a311f4cad0c750724299e9b72c268", + "data": "0x379607f50000000000000000000000000000000000000000000000000000000000000005", + "r": "0x006ab6dda9f4df56ea45583af36660329147f1753f3724ea5eb9ed83e812ca77", + "s": "0x495701e230667832c8999e884e366a61028633ecf951e8cd66d119f381ae5718", + "v": "28", + "value": "" + } } }