diff --git a/Changelog.md b/Changelog.md index f0f8b8485..886f91097 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,9 +1,13 @@ ### 0.8.17 (unreleased) Important Bugfixes: + * Array literals are convertible to both dynamically-sized and statically-sized arrays. + * String literals being part of an array literal can be implicitly converted into bytes. Language Features: + * Array literals are convertible to both dynamically-sized and statically-sized arrays. + * String literals being part of an array literal can be implicitly converted into bytes. Compiler Features: diff --git a/docs/types/reference-types.rst b/docs/types/reference-types.rst index a6b5728c9..20134c490 100644 --- a/docs/types/reference-types.rst +++ b/docs/types/reference-types.rst @@ -237,96 +237,68 @@ Array Literals ^^^^^^^^^^^^^^ An array literal is a comma-separated list of one or more expressions, enclosed -in square brackets (``[...]``). For example ``[1, a, f(3)]``. The type of the -array literal is determined as follows: - -It is always a statically-sized memory array whose length is the -number of expressions. - -The base type of the array is the type of the first expression on the list such that all -other expressions can be implicitly converted to it. It is a type error -if this is not possible. - -It is not enough that there is a type all the elements can be converted to. One of the elements -has to be of that type. - -In the example below, the type of ``[1, 2, 3]`` is -``uint8[3] memory``, because the type of each of these constants is ``uint8``. If -you want the result to be a ``uint[3] memory`` type, you need to convert -the first element to ``uint``. +in square brackets (``[...]``). For example ``[1, a, f(3)]``. It can be converted to +statically and dynamically-sized array if all its expressions can be implicitly +converted to the base type of the array. In the example below, the conversion is impossible +because ``-1`` cannot be implicitly converted to ``uint8``. .. code-block:: solidity // SPDX-License-Identifier: GPL-3.0 - pragma solidity >=0.4.16 <0.9.0; + pragma solidity >=0.8.16 <0.9.0; + // This will not compile contract C { function f() public pure { - g([uint(1), 2, 3]); + // The next line creates a type error because int_const -1 + // cannot be converted to uint8 memory. + g([1, -1]); + } + function g(uint8[] memory) public pure { + // ... + } + } + +A conversion into a statically-sized array can only be performed if the length of the array matches +the number of the expressions in the array literal. Therefore following is impossible: + +.. code-block:: solidity + + // SPDX-License-Identifier: GPL-3.0 + pragma solidity >=0.8.16 <0.9.0; + + // This will not compile + contract C { + function f() public pure { + // The next line creates a type error because inline_array(int_const 1, int_const 1) + // cannot be converted to uint[3] memory. + g([1, 2]); } function g(uint[3] memory) public pure { // ... } } -The array literal ``[1, -1]`` is invalid because the type of the first expression -is ``uint8`` while the type of the second is ``int8`` and they cannot be implicitly -converted to each other. To make it work, you can use ``[int8(1), -1]``, for example. - -Since fixed-size memory arrays of different type cannot be converted into each other -(even if the base types can), you always have to specify a common base type explicitly -if you want to use two-dimensional array literals: +Array literals can also be used to initialize multi-dimensional arrays: .. code-block:: solidity // SPDX-License-Identifier: GPL-3.0 - pragma solidity >=0.4.16 <0.9.0; + pragma solidity >=0.8.16 <0.9.0; contract C { - function f() public pure returns (uint24[2][4] memory) { - uint24[2][4] memory x = [[uint24(0x1), 1], [0xffffff, 2], [uint24(0xff), 3], [uint24(0xffff), 4]]; - // The following does not work, because some of the inner arrays are not of the right type. - // uint[2][4] memory x = [[0x1, 1], [0xffffff, 2], [0xff, 3], [0xffff, 4]]; - return x; + int256 [2][] a = [[1, 2], [3, 4], [5, 6]]; + + function f() public pure returns (uint8[][] memory) { + return [[1], [2, 3], [4, 5]]; } } -Fixed size memory arrays cannot be assigned to dynamically-sized -memory arrays, i.e. the following is not possible: - -.. code-block:: solidity - - // SPDX-License-Identifier: GPL-3.0 - pragma solidity >=0.4.0 <0.9.0; - - // This will not compile. - contract C { - function f() public { - // The next line creates a type error because uint[3] memory - // cannot be converted to uint[] memory. - uint[] memory x = [uint(1), 3, 4]; - } - } - -It is planned to remove this restriction in the future, but it creates some -complications because of how arrays are passed in the ABI. - -If you want to initialize dynamically-sized arrays, you have to assign the -individual elements: - -.. code-block:: solidity - - // SPDX-License-Identifier: GPL-3.0 - pragma solidity >=0.4.16 <0.9.0; - - contract C { - function f() public pure { - uint[] memory x = new uint[](3); - x[0] = 1; - x[1] = 3; - x[2] = 4; - } - } +.. note:: + Until Solidity 0.8.15, array literals were always a statically-sized memory array whose length + was the number of expressions. The base type of the array was the type of the first expression + on the list such that all other expressions could be implicitly converted to it. It was a type + error if the conversion was not possible. .. index:: ! array;length, length, push, pop, !array;push, !array;pop @@ -375,7 +347,7 @@ Array Members .. code-block:: solidity // SPDX-License-Identifier: GPL-3.0 - pragma solidity >=0.6.0 <0.9.0; + pragma solidity >=0.8.16 <0.9.0; contract ArrayContract { uint[2**20] aLotOfIntegers; @@ -457,12 +429,14 @@ Array Members } function createMemoryArray(uint size) public pure returns (bytes memory) { - // Dynamic memory arrays are created using `new`: - uint[2][] memory arrayOfPairs = new uint[2][](size); + // Dynamically-sized memory arrays are created using `new`: + uint[2][] memory arrayOfPairs1 = new uint[2][](size); - // Inline arrays are always statically-sized and if you only - // use literals, you have to provide at least one type. - arrayOfPairs[0] = [uint(1), 2]; + // Dynamically-sized memory arrays can be created with array literals as well: + uint[2][] memory arrayOfPairs2 = [[1, 2], [3, 4], [5, 6]]; + + require(arrayOfPairs1.length == arrayOfPairs2.length); + require(arrayOfPairs2[1][1] == 4); // Create a dynamic byte array: bytes memory b = new bytes(200); diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index de3cdc847..548a3e98e 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -67,6 +67,13 @@ bool TypeChecker::typeSupportedByOldABIEncoder(Type const& _type, bool _isLibrar if (!typeSupportedByOldABIEncoder(*base, _isLibraryCall) || (base->category() == Type::Category::Array && base->isDynamicallySized())) return false; } + if (_type.category() == Type::Category::InlineArray) + { + auto const& mobileType = dynamic_cast(_type).mobileType(); + if (!mobileType) + return false; + return typeSupportedByOldABIEncoder(*mobileType, _isLibraryCall); + } return true; } @@ -1647,7 +1654,6 @@ bool TypeChecker::visit(TupleExpression const& _tuple) else { bool isPure = true; - Type const* inlineArrayType = nullptr; for (size_t i = 0; i < components.size(); ++i) { @@ -1662,7 +1668,8 @@ bool TypeChecker::visit(TupleExpression const& _tuple) { if (_tuple.isInlineArray()) m_errorReporter.fatalTypeError(5604_error, components[i]->location(), "Array component cannot be empty."); - m_errorReporter.typeError(6473_error, components[i]->location(), "Tuple component cannot be empty."); + else + m_errorReporter.typeError(6473_error, components[i]->location(), "Tuple component cannot be empty."); } // Note: code generation will visit each of the expression even if they are not assigned from. @@ -1670,48 +1677,37 @@ bool TypeChecker::visit(TupleExpression const& _tuple) if (!dynamic_cast(*types[i]).mobileType()) m_errorReporter.fatalTypeError(3390_error, components[i]->location(), "Invalid rational number."); - if (_tuple.isInlineArray()) + if (_tuple.isInlineArray() && + types[i]->category() != Type::Category::InlineArray) { - solAssert(!!types[i], "Inline array cannot have empty components"); - - if ((i == 0 || inlineArrayType) && !types[i]->mobileType()) + Type const* mobileType = types[i]->mobileType(); + if (!mobileType) m_errorReporter.fatalTypeError(9563_error, components[i]->location(), "Invalid mobile type."); - - if (i == 0) - inlineArrayType = types[i]->mobileType(); - else if (inlineArrayType) - inlineArrayType = Type::commonType(inlineArrayType, types[i]); + else if (!mobileType->nameable()) + m_errorReporter.fatalTypeError( + 9656_error, + _tuple.location(), + "Unable to deduce nameable type for array elements. Try adding explicit type conversion for the first element." + ); + else if (mobileType->containsNestedMapping()) + m_errorReporter.fatalTypeError( + 1545_error, + _tuple.location(), + "Type " + types[i]->humanReadableName() + " is only valid in storage." + ); } + if (!*components[i]->annotation().isPure) isPure = false; } _tuple.annotation().isPure = isPure; - if (_tuple.isInlineArray()) - { - if (!inlineArrayType) - m_errorReporter.fatalTypeError(6378_error, _tuple.location(), "Unable to deduce common type for array elements."); - else if (!inlineArrayType->nameable()) - m_errorReporter.fatalTypeError( - 9656_error, - _tuple.location(), - "Unable to deduce nameable type for array elements. Try adding explicit type conversion for the first element." - ); - else if (inlineArrayType->containsNestedMapping()) - m_errorReporter.fatalTypeError( - 1545_error, - _tuple.location(), - "Type " + inlineArrayType->humanReadableName() + " is only valid in storage." - ); - _tuple.annotation().type = TypeProvider::array(DataLocation::Memory, inlineArrayType, types.size()); - } + if (_tuple.isInlineArray()) + _tuple.annotation().type = TypeProvider::inlineArray(move(types)); + else if (components.size() == 1) + _tuple.annotation().type = type(*components[0]); else - { - if (components.size() == 1) - _tuple.annotation().type = type(*components[0]); - else - _tuple.annotation().type = TypeProvider::tuple(move(types)); - } + _tuple.annotation().type = TypeProvider::tuple(move(types)); _tuple.annotation().isLValue = false; } @@ -2105,10 +2101,9 @@ void TypeChecker::typeCheckABIEncodeFunctions( } // Check additional arguments for variadic functions - vector> const& arguments = _functionCall.arguments(); - for (size_t i = 0; i < arguments.size(); ++i) + for (auto const& argument: _functionCall.arguments()) { - auto const& argType = type(*arguments[i]); + Type const* argType = type(*argument); if (argType->category() == Type::Category::RationalNumber) { @@ -2117,7 +2112,7 @@ void TypeChecker::typeCheckABIEncodeFunctions( { m_errorReporter.typeError( 6090_error, - arguments[i]->location(), + argument->location(), "Fractional numbers cannot yet be encoded." ); continue; @@ -2126,7 +2121,7 @@ void TypeChecker::typeCheckABIEncodeFunctions( { m_errorReporter.typeError( 8009_error, - arguments[i]->location(), + argument->location(), "Invalid rational number (too large or division by zero)." ); continue; @@ -2135,7 +2130,7 @@ void TypeChecker::typeCheckABIEncodeFunctions( { m_errorReporter.typeError( 7279_error, - arguments[i]->location(), + argument->location(), "Cannot perform packed encoding for a literal." " Please convert it to an explicit type first." ); @@ -2147,7 +2142,7 @@ void TypeChecker::typeCheckABIEncodeFunctions( { m_errorReporter.typeError( 9578_error, - arguments[i]->location(), + argument->location(), "Type not supported in packed mode." ); continue; @@ -2156,7 +2151,7 @@ void TypeChecker::typeCheckABIEncodeFunctions( if (!argType->fullEncodingType(false, abiEncoderV2, !_functionType->padArguments())) m_errorReporter.typeError( 2056_error, - arguments[i]->location(), + argument->location(), "This type cannot be encoded." ); } @@ -3361,6 +3356,16 @@ bool TypeChecker::visit(IndexAccess const& _access) isLValue = actualType.location() != DataLocation::CallData; break; } + case Type::Category::InlineArray: + { + if (!index) + m_errorReporter.typeError(5093_error, _access.location(), "Index expression cannot be omitted."); + else + expectType(*index, *TypeProvider::uint256()); + + resultType = dynamic_cast(*baseType).componentsCommonMobileType(); + break; + } case Type::Category::Mapping: { MappingType const& actualType = dynamic_cast(*baseType); @@ -3469,6 +3474,8 @@ bool TypeChecker::visit(IndexRangeAccess const& _access) ArrayType const* arrayType = nullptr; if (auto const* arraySlice = dynamic_cast(exprType)) arrayType = &arraySlice->arrayType(); + else if (auto const* inlineArray = dynamic_cast(exprType)) + arrayType = TypeProvider::array(DataLocation::Memory, inlineArray->componentsCommonMobileType(), inlineArray->components().size()); else if (!(arrayType = dynamic_cast(exprType))) m_errorReporter.fatalTypeError(4781_error, _access.location(), "Index range access is only possible for arrays and array slices."); diff --git a/libsolidity/ast/TypeProvider.cpp b/libsolidity/ast/TypeProvider.cpp index 97d230d2e..7c734f041 100644 --- a/libsolidity/ast/TypeProvider.cpp +++ b/libsolidity/ast/TypeProvider.cpp @@ -407,6 +407,11 @@ TupleType const* TypeProvider::tuple(vector members) return createAndGet(move(members)); } +InlineArrayType const* TypeProvider::inlineArray(vector _members) +{ + return createAndGet(move(_members)); +} + ReferenceType const* TypeProvider::withLocation(ReferenceType const* _type, DataLocation _location, bool _isPointer) { if (_type->location() == _location && _type->isPointer() == _isPointer) diff --git a/libsolidity/ast/TypeProvider.h b/libsolidity/ast/TypeProvider.h index b089ff1ef..56db316b3 100644 --- a/libsolidity/ast/TypeProvider.h +++ b/libsolidity/ast/TypeProvider.h @@ -109,6 +109,8 @@ public: static TupleType const* emptyTuple() noexcept { return &m_emptyTuple; } + static InlineArrayType const* inlineArray(std::vector members); + static ReferenceType const* withLocation(ReferenceType const* _type, DataLocation _location, bool _isPointer); /// @returns a copy of @a _type having the same location as this (and is not a pointer type) diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index f88d1afce..c8e606b40 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -323,6 +323,7 @@ Type const* Type::fullEncodingType(bool _inLibraryCall, bool _encoderV2, bool) c if (_inLibraryCall && encodingType && encodingType->dataStoredIn(DataLocation::Storage)) return encodingType; Type const* baseType = encodingType; + while (auto const* arrayType = dynamic_cast(baseType)) { baseType = arrayType->baseType(); @@ -2740,6 +2741,101 @@ Type const* TupleType::mobileType() const return TypeProvider::tuple(move(mobiles)); } +BoolResult InlineArrayType::isImplicitlyConvertibleTo(Type const& _other) const +{ + auto arrayType = dynamic_cast(&_other); + + if (!arrayType || arrayType->isByteArrayOrString()) + return BoolResult::err("Array literal can not be converted to byte array or string."); + + if (!arrayType->isDynamicallySized() && arrayType->length() != components().size()) + return BoolResult::err( + "Number of components in array literal (" + to_string(components().size()) + ") " + + "does not match array size (" + to_string(arrayType->length().convert_to()) + ")." + ); + + for (Type const* c: components()) + { + BoolResult result = c->isImplicitlyConvertibleTo(*arrayType->baseType()); + if (!result) + return BoolResult::err( + "Invalid conversion from " + c->toString(false) + + " to " + arrayType->baseType()->toString(false) + "." + + (result.message().empty() ? "" : " ") + result.message() + ); + } + + return true; +} + +string InlineArrayType::richIdentifier() const +{ + return "t_inline_array" + identifierList(components()); +} + +bool InlineArrayType::operator==(Type const& _other) const +{ + if (auto inlineArrayType = dynamic_cast(&_other)) + { + std::vector const& otherComponents = inlineArrayType->components(); + if (m_components.size() != otherComponents.size()) + return false; + + for (unsigned i = 0; i < m_components.size(); ++i) + if (!(*m_components[i] == *otherComponents[i])) + return false; + + return true; + } + else + return false; +} + +string InlineArrayType::toString(bool _short) const +{ + vector result; + for (auto const& t: components()) + result.push_back(t->toString(_short)); + return "inline_array(" + util::joinHumanReadable(result) + ")"; +} + +u256 InlineArrayType::storageSize() const +{ + solAssert(false, "Storage size of non-storable InlineArrayType type requested."); +} + +Type const* InlineArrayType::mobileType() const +{ + Type const* baseType = componentsCommonMobileType(); + return + baseType ? + TypeProvider::array(DataLocation::Memory, baseType, components().size()) : + nullptr; + } + +Type const* InlineArrayType::componentsCommonMobileType() const +{ + solAssert(!m_components.empty(), "Empty array literal"); + Type const* commonType = nullptr; + + for (Type const* type: m_components) + commonType = + commonType ? + Type::commonType(commonType, type->mobileType()) : + type->mobileType(); + + return TypeProvider::withLocationIfReference(DataLocation::Memory, commonType); +} + +vector> InlineArrayType::makeStackItems() const +{ + vector> slots; + for (auto && [index, type]: components() | ranges::views::enumerate) + slots.emplace_back("component_" + std::to_string(index + 1), type); + return slots; +} + + FunctionType::FunctionType(FunctionDefinition const& _function, Kind _kind): m_kind(_kind), m_stateMutability(_function.stateMutability()), diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 553af6c7d..3cedb1d38 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -175,7 +175,7 @@ public: enum class Category { Address, Integer, RationalNumber, StringLiteral, Bool, FixedPoint, Array, ArraySlice, - FixedBytes, Contract, Struct, Function, Enum, UserDefinedValueType, Tuple, + FixedBytes, Contract, Struct, Function, Enum, UserDefinedValueType, Tuple, InlineArray, Mapping, TypeType, Modifier, Magic, Module, InaccessibleDynamic }; @@ -1204,6 +1204,41 @@ private: std::vector const m_components; }; +class InlineArrayType: public CompositeType +{ +public: + explicit InlineArrayType(std::vector _types): m_components(std::move(_types)) {} + + Category category() const override { return Category::InlineArray; } + + BoolResult isImplicitlyConvertibleTo(Type const& _other) const override; + std::string richIdentifier() const override; + bool operator==(Type const& _other) const override; + TypeResult binaryOperatorResult(Token, Type const*) const override { return nullptr; } + std::string toString(bool) const override; + bool canBeStored() const override { return false; } + u256 storageSize() const override; + bool hasSimpleZeroValueInMemory() const override { return false; } + Type const* mobileType() const override; + Type const* componentsCommonMobileType() const; + std::vector const& components() const { return m_components; } + +protected: + std::vector> makeStackItems() const override; + std::vector decomposition() const override + { + // Currently calling InlineArrayType::decomposition() is not expected, because we cannot declare a variable of a InlineArrayType type. + // If that changes, before removing the solAssert, make sure the function does the right thing and is used properly. + // Note that different InlineArrayType members can have different data locations, so using decomposition() to check + // the tuple validity for a data location might require special care. + solUnimplemented("InlineArray decomposition is not expected."); + return m_components; + } + +private: + std::vector const m_components; +}; + /** * The type of a function, identified by its (return) parameter types. * @todo the return parameters should also have names, i.e. return parameters should be a struct diff --git a/libsolidity/codegen/ABIFunctions.cpp b/libsolidity/codegen/ABIFunctions.cpp index c92525f21..b76b485b0 100644 --- a/libsolidity/codegen/ABIFunctions.cpp +++ b/libsolidity/codegen/ABIFunctions.cpp @@ -29,6 +29,7 @@ #include #include +#include using namespace std; using namespace solidity; @@ -286,6 +287,16 @@ string ABIFunctions::abiEncodingFunction( if (_from.category() == Type::Category::StringLiteral) return abiEncodingFunctionStringLiteral(_from, to, _options); + else if (_from.category() == Type::Category::InlineArray) + { + solAssert(_to.category() == Type::Category::Array); + return abiEncodingFunctionInlineArray( + dynamic_cast(_from), + dynamic_cast(_to), + _options + ); + + } else if (auto toArray = dynamic_cast(&to)) { ArrayType const* fromArray = nullptr; @@ -632,6 +643,87 @@ string ABIFunctions::abiEncodingFunctionSimpleArray( }); } +string ABIFunctions::abiEncodingFunctionInlineArray( + InlineArrayType const& _from, + ArrayType const& _to, + EncodingOptions const& _options +) +{ + string functionName = + "abi_encode_" + + _from.identifier() + + "_to_" + + _to.identifier() + + _options.toFunctionNameSuffix(); + + return createFunction(functionName, [&]() { + bool dynamic = _to.isDynamicallyEncoded(); + bool dynamicBase = _to.baseType()->isDynamicallyEncoded(); + bool const usesTail = dynamicBase && !_options.dynamicInplace; + EncodingOptions subOptions(_options); + subOptions.encodeFunctionFromStack = true; + subOptions.padded = true; + + vector> memberSetValues; + unsigned stackItemIndex = 0; + + for (auto const& type: _from.components()) + { + memberSetValues.emplace_back(); + + memberSetValues.back()["setMember"] = Whiskers(R"( + + mstore(pos, sub(tail, headStart)) + tail := (, tail) + pos := add(pos, 0x20) + + pos := (, pos) + + )") + ("value", suffixedVariableNameList("var_", stackItemIndex, stackItemIndex + type->sizeOnStack())) + ("usesTail", usesTail) + ("encodeToMemoryFun", abiEncodeAndReturnUpdatedPosFunction(*type, *_to.baseType(), subOptions)) + .render(); + + stackItemIndex += type->sizeOnStack(); + } + + return Whiskers(R"( + // -> + function (, pos) { + let length := + pos := (pos, length) + + + let headStart := pos + let tail := add(pos, mul(length, 0x20)) + <#member> + + + pos := tail + + <#member> + + + + + + } + )") + ("functionName", functionName) + ("member", std::move(memberSetValues)) + ("length", to_string(_from.components().size())) + ("readableTypeNameFrom", _from.toString(true)) + ("readableTypeNameTo", _to.toString(true)) + ("return", dynamic ? " -> end " : "") + ("assignEnd", dynamic ? "end := pos" : "") + ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options)) + ("usesTail", usesTail) + ("values", suffixedVariableNameList("var_", 0, _from.sizeOnStack())) + .render(); + }); +} + string ABIFunctions::abiEncodingFunctionMemoryByteArray( ArrayType const& _from, ArrayType const& _to, diff --git a/libsolidity/codegen/ABIFunctions.h b/libsolidity/codegen/ABIFunctions.h index b7f7f8ae5..692dd0a96 100644 --- a/libsolidity/codegen/ABIFunctions.h +++ b/libsolidity/codegen/ABIFunctions.h @@ -190,6 +190,11 @@ private: ArrayType const& _targetType, EncodingOptions const& _options ); + std::string abiEncodingFunctionInlineArray( + InlineArrayType const& _givenType, + ArrayType const& _targetType, + EncodingOptions const& _options + ); std::string abiEncodingFunctionMemoryByteArray( ArrayType const& _givenType, ArrayType const& _targetType, diff --git a/libsolidity/codegen/ArrayUtils.cpp b/libsolidity/codegen/ArrayUtils.cpp index 9b95463ba..c3b12ed2c 100644 --- a/libsolidity/codegen/ArrayUtils.cpp +++ b/libsolidity/codegen/ArrayUtils.cpp @@ -36,6 +36,9 @@ #include #include +#include +#include + using namespace std; using namespace solidity; using namespace solidity::evmasm; @@ -268,20 +271,13 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons << swapInstruction(1 + byteOffsetSize); _context.appendJumpTo(copyLoopStart); _context << copyLoopEnd; + if (haveByteOffsetTarget) { - // clear elements that might be left over in the current slot in target - // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end target_byte_offset [source_byte_offset] - _context << dupInstruction(byteOffsetSize) << Instruction::ISZERO; - evmasm::AssemblyItem copyCleanupLoopEnd = _context.appendConditionalJump(); - _context << dupInstruction(2 + byteOffsetSize) << dupInstruction(1 + byteOffsetSize); - StorageItem(_context, *targetBaseType).setToZero(SourceLocation(), true); - utils.incrementByteOffset(targetBaseType->storageBytes(), byteOffsetSize, byteOffsetSize + 2); - _context.appendJumpTo(copyLoopEnd); - - _context << copyCleanupLoopEnd; + utils.clearLeftoversInSlot(*targetBaseType, byteOffsetSize, 2 + byteOffsetSize); _context << Instruction::POP; // might pop the source, but then target is popped next } + if (haveByteOffsetSource) _context << Instruction::POP; _context << copyLoopEndWithoutByteOffset; @@ -298,6 +294,188 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons } ); } +void ArrayUtils::clearLeftoversInSlot(Type const& _type, unsigned _byteOffsetPosition, unsigned _storageOffsetPosition) const +{ + // clear elements that might be left over in the current slot in target + // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end target_byte_offset [source_byte_offset] + + auto cleanupLoopStart = m_context.newTag(); + m_context << cleanupLoopStart; + + m_context << dupInstruction(_byteOffsetPosition) << Instruction::ISZERO; + evmasm::AssemblyItem copyCleanupLoopEnd = m_context.appendConditionalJump(); + m_context << dupInstruction(_storageOffsetPosition) << dupInstruction(_byteOffsetPosition + 1); + StorageItem(m_context, _type).setToZero(SourceLocation(), true); + incrementByteOffset(_type.storageBytes(), _byteOffsetPosition, _storageOffsetPosition); + m_context.appendJumpTo(cleanupLoopStart); + + m_context << copyCleanupLoopEnd; +} + +void ArrayUtils::moveInlineArrayToStorage( + ArrayType const& _targetType, + InlineArrayType const& _sourceType, + unsigned _sourcePosition) const +{ + // stack: source... ... target_ref + solAssert(!_targetType.containsNestedMapping()); + solAssert(!_targetType.isByteArrayOrString()); + solAssert(_sourceType.components().size() > 0); + + Type const* arrayBaseType = _targetType.baseType(); + bool const hasByteOffset = arrayBaseType->storageBytes() <= 16; + + if (_targetType.isDynamicallySized()) + { + m_context << u256(_sourceType.components().size()); + // stack: source... ... target_ref source_length + m_context << Instruction::DUP2 << Instruction::SSTORE; + // stack: source... ... target_ref + m_context << Instruction::DUP1; + CompilerUtils(m_context).computeHashStatic(); + ++_sourcePosition; + // stack: source... ... target_ref target_data_pos + } + + for (auto&& [index, sourceComponentType]: + _sourceType.components() | ranges::views::enumerate | ranges::views::reverse) + { + solAssert(arrayBaseType->nameable(), ""); + + if (ArrayType const* targetType = dynamic_cast(_targetType.baseType())) + { + // stack: source... ... target_ref target_data_pos + m_context + << Instruction::DUP1 + << u256(targetType->storageSize() * index) << Instruction::ADD; + // stack: source... ... target_ref target_data_pos component_data_ref + + if (StringLiteralType const* stringLiteralType = dynamic_cast(sourceComponentType)) + copyLiteralToStorage(*stringLiteralType); + else + { + InlineArrayType const* sourceType = dynamic_cast(sourceComponentType); + solAssert(sourceType); + moveInlineArrayToStorage(*targetType, *sourceType, _sourcePosition + 1); + // stack: source... ... target_ref target_data_pos component_data_ref + } + + m_context << Instruction::POP; + // stack: source... ... target_ref target_data_pos + } + else + { + // stack: source... ... target_ref target_data_pos + CompilerUtils(m_context).moveToStackTop( + _sourcePosition, + sourceComponentType->sizeOnStack() + ); + + // stack: source... ... target_ref target_data_pos value... + m_context << dupInstruction(1 + sourceComponentType->sizeOnStack()); + + Type const* stackType = sourceComponentType; + if (RationalNumberType const* rType = dynamic_cast(sourceComponentType)) + { + solUnimplementedAssert(!rType->isFractional(), "Not yet implemented - FixedPointType."); + stackType = rType->integerType(); + CompilerUtils(m_context).convertType(*sourceComponentType, *stackType); + } + + // stack: source... ... target_ref target_data_pos value... target_data_pos + computeStoragePosition(static_cast(index), arrayBaseType->storageBytes()); + // stack: source... ... target_ref target_data_pos value... target_data_pos offset + + if (index == _sourceType.components().size() && hasByteOffset) + { + m_context << Instruction::DUP2 << Instruction::DUP2; + clearLeftoversInSlot(*arrayBaseType, 1, 2); + m_context << Instruction::POP << Instruction::POP; + } + + StorageItem(m_context, *arrayBaseType) + .storeValue(*stackType, SourceLocation(), true); + // stack: source... ... target_ref target_data_pos + } + } + + // stack: ... target_ref target_data_pos + if (_targetType.isDynamicallySized()) + m_context << Instruction::POP; + // stack: ... target_ref +} + +void ArrayUtils::copyLiteralToStorage(StringLiteralType const& _sourceType) const +{ + bytesConstRef data(_sourceType.value()); + // stack: target_ref + if (data.empty()) + m_context << u256(0) << Instruction::DUP2 << Instruction::SSTORE; + else if (data.size() < 32) + { + // stack: target_ref + m_context + << u256(util::h256(data, util::h256::AlignLeft)) + << u256(2) + << u256(data.size()) + << Instruction::MUL + << Instruction::ADD; + // stack: target_ref value + m_context << Instruction::DUP2 << Instruction::SSTORE; + // stack: target_ref + } + else + { + // stack: target_ref + m_context << Instruction::DUP1; + // stack: target_ref target_ref + m_context + << u256(1) << u256(2) << u256(data.size()) + << Instruction::MUL << Instruction::ADD; + // stack: target_ref target_ref 2*length+1 + m_context << Instruction::DUP2 << Instruction::SSTORE; + // stack: target_ref target_ref + CompilerUtils(m_context).computeHashStatic(); + // stack: target_ref target_data_pos + + for (size_t index = 0; index <= data.size() / 32; ++index) + { + // stack: target_ref target_data_pos + size_t const chunk = min(32, data.size() - index * 32); + m_context << u256(util::h256(data.cropped(index * 32, chunk), util::h256::AlignLeft)); + // stack: target_ref target_data_pos value + m_context << Instruction::DUP2 << Instruction::SSTORE; + // stack: target_ref target_data_pos + m_context << u256(1) << Instruction::ADD; + // stack: target_ref target_data_pos+1 + } + m_context << Instruction::POP; + // stack: target_ref + } +} + +void ArrayUtils::computeStoragePosition(unsigned _index, unsigned _byteSize) const +{ + // We do the following calculation: + // slot = element_index * element_byte_size / 32 + // offset = element_index * element_byte_size % 32 + + // stack: slot + m_context << u256(_byteSize) << u256(_index) << Instruction::MUL; + // stack: slot byte_pos + + m_context << Instruction::SWAP1; + // stack: byte_pos slot + + m_context << u256(32) << Instruction::DUP3 << Instruction::DIV; + // stack: byte_pos slot slot_offset + + m_context << Instruction::ADD << Instruction::SWAP1; + // stack: target_slot byte_pos + + m_context << u256(32) << Instruction::SWAP1 << Instruction::MOD; + // stack: target_slot offset +} void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWordBoundaries) const { @@ -535,6 +713,52 @@ void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWord } } +void ArrayUtils::moveInlineArrayToMemory( + InlineArrayType const& _sourceType, + ArrayType const& _targetType, + unsigned _sourcePosition, + bool _padToWordBoundaries) const +{ + auto const& components = _sourceType.components(); + u256 const memoryStride = + _padToWordBoundaries ? + (_targetType.memoryStride() + 31) / 32 * 32 : + _targetType.memoryStride(); + + // value... ... target_pos + m_context << u256(components.size() * memoryStride) << Instruction::ADD; + // value... ... target_pos_end + m_context << Instruction::DUP1; + // value... ... target_pos_end target_pos_end + + CompilerUtils utils(m_context); + for (Type const* component: components | ranges::views::reverse) + { + solAssert( + component->category() != Type::Category::InlineArray && + component->category() != Type::Category::Array && + component->category() != Type::Category::ArraySlice); + + // values... ... target_pos_end component_pos_end + m_context << memoryStride << Instruction::SWAP1 << Instruction::SUB; + // values... ... target_pos_end component_pos + m_context << Instruction::DUP1; + // values... ... target_pos_end component_pos component_pos + utils.moveToStackTop(_sourcePosition + 2, component->sizeOnStack()); + // values... ... target_pos_end component_pos component_pos value + utils.convertType(*component, *_targetType.baseType()); + // values... ... target_pos_end component_pos component_pos converted_value + utils.storeInMemoryDynamic(*_targetType.baseType()); + // values... ... target_pos_end component_pos component_pos_end + m_context << Instruction::POP; + // values... ... target_pos_end component_pos + } + + // ... target_pos_end component_pos + m_context << Instruction::POP; + // ... target_pos_end +} + void ArrayUtils::clearArray(ArrayType const& _typeIn) const { Type const* type = &_typeIn; diff --git a/libsolidity/codegen/ArrayUtils.h b/libsolidity/codegen/ArrayUtils.h index 7a47efad7..b729a81fd 100644 --- a/libsolidity/codegen/ArrayUtils.h +++ b/libsolidity/codegen/ArrayUtils.h @@ -28,9 +28,11 @@ namespace solidity::frontend { -class CompilerContext; -class Type; class ArrayType; +class CompilerContext; +class InlineArrayType; +class StringLiteralType; +class Type; /** * Class that provides code generation for handling arrays. @@ -45,6 +47,16 @@ public: /// Stack pre: source_reference [source_length] target_reference /// Stack post: target_reference void copyArrayToStorage(ArrayType const& _targetType, ArrayType const& _sourceType) const; + + /// Moves an inline array from the stack to the storage. + /// @param sourcePosition the stack offset of the source + /// Stack pre: source ... target_reference + /// Stack post: ... target_reference + void moveInlineArrayToStorage( + ArrayType const& _targetType, + InlineArrayType const& _sourceType, + unsigned _sourcePosition = 1) const; + /// Copies the data part of an array (which cannot be dynamically nested) from anywhere /// to a given position in memory. /// This always copies contained data as is (i.e. structs and fixed-size arrays are copied in @@ -53,6 +65,17 @@ public: /// Stack pre: memory_offset source_item /// Stack post: memory_offest + length(padded) void copyArrayToMemory(ArrayType const& _sourceType, bool _padToWordBoundaries = true) const; + + /// Moves inline array from the stack to a given position in memory. + /// @param sourcePosition the stack offset of the source + /// Stack pre: source ... target_reference + /// Stack post: ... target_reference + length(padded) + void moveInlineArrayToMemory( + InlineArrayType const& _sourceType, + ArrayType const& _targetType, + unsigned _sourcePosition, + bool _padToWordBoundaries = true) const; + /// Clears the given dynamic or static array. /// Stack pre: storage_ref storage_byte_offset /// Stack post: @@ -114,6 +137,29 @@ private: /// @param storageOffsetPosition the stack offset of the storage slot offset void incrementByteOffset(unsigned _byteSize, unsigned _byteOffsetPosition, unsigned _storageOffsetPosition) const; + /// Copy a string literal to the storage. + /// @param sourcePosition the stack offset of the source + /// Stack pre: target_reference + /// Stack post: target_reference + void copyLiteralToStorage(StringLiteralType const& _sourceType) const; + + /// Appends code that computes a storage position of the array element. + /// @param index array element index + /// @param byteSize array element size in bytes + /// Stack pre: slot + /// Stack post: target_slot byte_offset + void computeStoragePosition(unsigned _index, unsigned _byteSize) const; + + /// Appends code that set to zero all elements in slot starting at offset. + /// Slot and offset are updated to show next slot. + /// @param type element type + /// @param byteOffsetPosition the stack offset of the storage byte offset + /// @param storageOffsetPosition the stack offset of the storage slot offset + /// Stack pre: ... slot offset ... + /// Stack post: ... slot offset ... + void clearLeftoversInSlot(Type const& _type, unsigned _byteOffsetPosition, unsigned _storageOffsetPosition) const; + + CompilerContext& m_context; }; diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 32b1f1bba..48aaef98e 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -32,6 +32,7 @@ #include #include #include +#include using namespace std; using namespace solidity; @@ -462,6 +463,7 @@ void CompilerUtils::encodeToMemory( // store memory start pointer m_context << Instruction::DUP1; + ArrayUtils utils(m_context); unsigned argSize = CompilerUtils::sizeOnStack(_givenTypes); unsigned stackPos = 0; // advances through the argument values unsigned dynPointers = 0; // number of dynamic head pointers on the stack @@ -479,6 +481,14 @@ void CompilerUtils::encodeToMemory( StackTooDeepError, util::stackTooDeepString ); + stackPos += _givenTypes[i]->sizeOnStack(); + } + else if (InlineArrayType const* inlineArrayType = dynamic_cast(_givenTypes[i])) + { + ArrayType const* arrayType = dynamic_cast(_targetTypes[i]); + unsigned const sourceStackPosition = argSize - stackPos + dynPointers - inlineArrayType->sizeOnStack() + 2; + utils.moveInlineArrayToMemory(*inlineArrayType, *arrayType, sourceStackPosition, _padToWordBoundaries); + argSize -= inlineArrayType->sizeOnStack(); } else { @@ -520,8 +530,10 @@ void CompilerUtils::encodeToMemory( } else storeInMemoryDynamic(*type, _padToWordBoundaries, needCleanup); + stackPos += _givenTypes[i]->sizeOnStack(); } - stackPos += _givenTypes[i]->sizeOnStack(); + + } // now copy the dynamic part @@ -545,7 +557,18 @@ void CompilerUtils::encodeToMemory( m_context << dupInstruction(2 + dynPointers - thisDynPointer); m_context << Instruction::MSTORE; // stack: ... - if (_givenTypes[i]->category() == Type::Category::StringLiteral) + if (InlineArrayType const* inlineArrayType = dynamic_cast(_givenTypes[i])) + { + ArrayType const* arrayType = dynamic_cast(_targetTypes[i]); + + m_context << u256(inlineArrayType->components().size()); + storeInMemoryDynamic(*TypeProvider::uint256(), true); + + unsigned const sourceStackPosition = argSize - stackPos + dynPointers - inlineArrayType->sizeOnStack() + 2; + utils.moveInlineArrayToMemory(*inlineArrayType, *arrayType, sourceStackPosition, _padToWordBoundaries); + argSize -= inlineArrayType->sizeOnStack(); + } + else if (_givenTypes[i]->category() == Type::Category::StringLiteral) { auto const& strType = dynamic_cast(*_givenTypes[i]); auto const size = strType.value().size(); @@ -1114,6 +1137,62 @@ void CompilerUtils::convertType( } break; } + case Type::Category::InlineArray: + { + InlineArrayType const& inlineArray = dynamic_cast(_typeOnStack); + ArrayType const& arrayType = dynamic_cast(_targetType); + + solAssert(arrayType.location() == DataLocation::Memory); + + auto const& components = inlineArray.components(); + m_context << u256(components.size()); + // stack: + ArrayUtils(m_context).convertLengthToSize(arrayType, true); + + // stack: + if (arrayType.isDynamicallySized()) + m_context << u256(0x20) << Instruction::ADD; + // = + 0x20 + allocateMemory(); + + // stack: + m_context << Instruction::DUP1; + // stack: + if (arrayType.isDynamicallySized()) + { + m_context << u256(components.size()); + // stack: + storeInMemoryDynamic(*TypeProvider::uint256()); + // memory[] = + // stack: + } + + // stack: + m_context << u256(components.size() * arrayType.baseType()->memoryHeadSize()) << Instruction::ADD; + // stack: + for (Type const* component: components | ranges::views::reverse) + { + // stack: + m_context << u256(arrayType.memoryStride()) << Instruction::SWAP1 << Instruction::SUB; + // stack: + m_context << Instruction::DUP1; + // stack: + + unsigned const componentSize = component->sizeOnStack(); + moveToStackTop(3, componentSize); + // stack: + convertType(*component, *arrayType.baseType()); + // stack: + storeInMemoryDynamic(*arrayType.baseType()); + // stack: + m_context << Instruction::POP; + // stack: + } + // stack: + m_context << Instruction::POP; + // stack: + break; + } case Type::Category::ArraySlice: { auto& typeOnStack = dynamic_cast(_typeOnStack); @@ -1287,6 +1366,7 @@ void CompilerUtils::convertType( } break; } + case Type::Category::Bool: solAssert(_targetType == _typeOnStack, "Invalid conversion for bool."); if (_cleanupNeeded) diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 95087aa02..ae6f10625 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -73,6 +73,23 @@ Type const* closestType(Type const* _type, Type const* _targetType, bool _isShif } return TypeProvider::tuple(move(tempComponents)); } + else if (auto const* inlineArrayType = dynamic_cast(_type)) + { + auto targetArray = dynamic_cast(_targetType); + solAssert(targetArray); + + if (targetArray->isDynamicallySized()) + return TypeProvider::array( + DataLocation::Memory, + targetArray->baseType() + ); + else + return TypeProvider::array( + DataLocation::Memory, + targetArray->baseType(), + inlineArrayType->components().size() + ); + } else return _targetType->dataStoredIn(DataLocation::Storage) ? _type->mobileType() : _targetType; } @@ -93,18 +110,21 @@ void ExpressionCompiler::appendStateVariableInitialization(VariableDeclaration c CompilerContext::LocationSetter locationSetter(m_context, _varDecl); _varDecl.value()->accept(*this); - if (_varDecl.annotation().type->dataStoredIn(DataLocation::Storage)) + if (type->category() != Type::Category::InlineArray) { - // reference type, only convert value to mobile type and do final conversion in storeValue. - auto mt = type->mobileType(); - solAssert(mt, ""); - utils().convertType(*type, *mt); - type = mt; - } - else - { - utils().convertType(*type, *_varDecl.annotation().type); - type = _varDecl.annotation().type; + if (_varDecl.annotation().type->dataStoredIn(DataLocation::Storage)) + { + // reference type, only convert value to mobile type and do final conversion in storeValue. + auto mt = type->mobileType(); + solAssert(mt, ""); + utils().convertType(*type, *mt); + type = mt; + } + else + { + utils().convertType(*type, *_varDecl.annotation().type); + type = _varDecl.annotation().type; + } } if (_varDecl.immutable()) ImmutableItem(m_context, _varDecl).storeValue(*type, _varDecl.location(), true); @@ -365,44 +385,25 @@ bool ExpressionCompiler::visit(Assignment const& _assignment) bool ExpressionCompiler::visit(TupleExpression const& _tuple) { - if (_tuple.isInlineArray()) - { - ArrayType const& arrayType = dynamic_cast(*_tuple.annotation().type); - - solAssert(!arrayType.isDynamicallySized(), "Cannot create dynamically sized inline array."); - utils().allocateMemory(max(u256(32u), arrayType.memoryDataSize())); - m_context << Instruction::DUP1; - - for (auto const& component: _tuple.components()) + vector> lvalues; + for (auto const& component: _tuple.components()) + if (component) { - acceptAndConvert(*component, *arrayType.baseType(), true); - utils().storeInMemoryDynamic(*arrayType.baseType(), true); - } - - m_context << Instruction::POP; - } - else - { - vector> lvalues; - for (auto const& component: _tuple.components()) - if (component) + component->accept(*this); + if (_tuple.annotation().willBeWrittenTo) { - component->accept(*this); - if (_tuple.annotation().willBeWrittenTo) - { - solAssert(!!m_currentLValue, ""); - lvalues.push_back(move(m_currentLValue)); - } + solAssert(!!m_currentLValue, ""); + lvalues.push_back(move(m_currentLValue)); } - else if (_tuple.annotation().willBeWrittenTo) - lvalues.push_back(unique_ptr()); - if (_tuple.annotation().willBeWrittenTo) - { - if (_tuple.components().size() == 1) - m_currentLValue = move(lvalues[0]); - else - m_currentLValue = make_unique(m_context, move(lvalues)); } + else if (_tuple.annotation().willBeWrittenTo) + lvalues.push_back(unique_ptr()); + if (_tuple.annotation().willBeWrittenTo) + { + if (_tuple.components().size() == 1) + m_currentLValue = move(lvalues[0]); + else + m_currentLValue = make_unique(m_context, move(lvalues)); } return false; } @@ -2127,6 +2128,27 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) } break; } + case Type::Category::InlineArray: + { + InlineArrayType const& inlineArrayType = dynamic_cast(baseType); + solAssert(_indexAccess.indexExpression(), "Index expression expected."); + + ArrayType const* arrayType = TypeProvider::array(DataLocation::Memory, inlineArrayType.componentsCommonMobileType(), inlineArrayType.components().size()); + + // stack layout: (variably sized) + acceptAndConvert(_indexAccess.baseExpression(), *arrayType, true); + utils().moveIntoStack(inlineArrayType.sizeOnStack()); + utils().popStackSlots(inlineArrayType.sizeOnStack()); + // stack layout: [] + + acceptAndConvert(*_indexAccess.indexExpression(), *TypeProvider::uint256(), true); + // stack layout: [] + + ArrayUtils(m_context).accessIndex(*arrayType, true); + setLValue(_indexAccess, *arrayType->baseType()); + + break; + } case Type::Category::FixedBytes: { FixedBytesType const& fixedBytesType = dynamic_cast(baseType); diff --git a/libsolidity/codegen/LValue.cpp b/libsolidity/codegen/LValue.cpp index aa7524c93..f6db5cc4e 100644 --- a/libsolidity/codegen/LValue.cpp +++ b/libsolidity/codegen/LValue.cpp @@ -359,86 +359,100 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc } else { - solAssert( - _sourceType.category() == m_dataType->category(), - "Wrong type conversation for assignment." - ); - if (m_dataType->category() == Type::Category::Array) + if (_sourceType.category() == Type::Category::InlineArray) { m_context << Instruction::POP; // remove byte offset - ArrayUtils(m_context).copyArrayToStorage( + ArrayUtils(m_context).moveInlineArrayToStorage( dynamic_cast(*m_dataType), - dynamic_cast(_sourceType) + dynamic_cast(_sourceType) ); if (_move) m_context << Instruction::POP; } - else if (m_dataType->category() == Type::Category::Struct) - { - // stack layout: source_ref target_ref target_offset - // note that we have structs, so offset should be zero and are ignored - m_context << Instruction::POP; - auto const& structType = dynamic_cast(*m_dataType); - auto const& sourceType = dynamic_cast(_sourceType); - solAssert( - structType.structDefinition() == sourceType.structDefinition(), - "Struct assignment with conversion." - ); - solAssert(!structType.containsNestedMapping(), ""); - if (sourceType.location() == DataLocation::CallData) - { - solAssert(sourceType.sizeOnStack() == 1, ""); - solAssert(structType.sizeOnStack() == 1, ""); - m_context << Instruction::DUP2 << Instruction::DUP2; - m_context.callYulFunction(m_context.utilFunctions().updateStorageValueFunction(sourceType, structType, 0), 2, 0); - } - else - { - for (auto const& member: structType.members(nullptr)) - { - // assign each member that can live outside of storage - Type const* memberType = member.type; - solAssert(memberType->nameable(), ""); - Type const* sourceMemberType = sourceType.memberType(member.name); - if (sourceType.location() == DataLocation::Storage) - { - // stack layout: source_ref target_ref - pair const& offsets = sourceType.storageOffsetsOfMember(member.name); - m_context << offsets.first << Instruction::DUP3 << Instruction::ADD; - m_context << u256(offsets.second); - // stack: source_ref target_ref source_member_ref source_member_off - StorageItem(m_context, *sourceMemberType).retrieveValue(_location, true); - // stack: source_ref target_ref source_value... - } - else - { - solAssert(sourceType.location() == DataLocation::Memory, ""); - // stack layout: source_ref target_ref - m_context << sourceType.memoryOffsetOfMember(member.name); - m_context << Instruction::DUP3 << Instruction::ADD; - MemoryItem(m_context, *sourceMemberType).retrieveValue(_location, true); - // stack layout: source_ref target_ref source_value... - } - unsigned stackSize = sourceMemberType->sizeOnStack(); - pair const& offsets = structType.storageOffsetsOfMember(member.name); - m_context << dupInstruction(1 + stackSize) << offsets.first << Instruction::ADD; - m_context << u256(offsets.second); - // stack: source_ref target_ref target_off source_value... target_member_ref target_member_byte_off - StorageItem(m_context, *memberType).storeValue(*sourceMemberType, _location, true); - } - } - // stack layout: source_ref target_ref - solAssert(sourceType.sizeOnStack() == 1, "Unexpected source size."); - if (_move) - utils.popStackSlots(2); - else - m_context << Instruction::SWAP1 << Instruction::POP; - } else - BOOST_THROW_EXCEPTION( - InternalCompilerError() - << errinfo_sourceLocation(_location) - << util::errinfo_comment("Invalid non-value type for assignment.")); + { + solAssert( + _sourceType.category() == m_dataType->category(), + "Wrong type conversation for assignment." + ); + + if (m_dataType->category() == Type::Category::Array) + { + m_context << Instruction::POP; // remove byte offset + ArrayUtils(m_context).copyArrayToStorage( + dynamic_cast(*m_dataType), + dynamic_cast(_sourceType) + ); + if (_move) + m_context << Instruction::POP; + } + else if (m_dataType->category() == Type::Category::Struct) + { + // stack layout: source_ref target_ref target_offset + // note that we have structs, so offset should be zero and are ignored + m_context << Instruction::POP; + auto const& structType = dynamic_cast(*m_dataType); + auto const& sourceType = dynamic_cast(_sourceType); + solAssert( + structType.structDefinition() == sourceType.structDefinition(), + "Struct assignment with conversion." + ); + solAssert(!structType.containsNestedMapping(), ""); + if (sourceType.location() == DataLocation::CallData) + { + solAssert(sourceType.sizeOnStack() == 1, ""); + solAssert(structType.sizeOnStack() == 1, ""); + m_context << Instruction::DUP2 << Instruction::DUP2; + m_context.callYulFunction(m_context.utilFunctions().updateStorageValueFunction(sourceType, structType, 0), 2, 0); + } + else + { + for (auto const& member: structType.members(nullptr)) + { + // assign each member that can live outside of storage + Type const* memberType = member.type; + solAssert(memberType->nameable(), ""); + Type const* sourceMemberType = sourceType.memberType(member.name); + if (sourceType.location() == DataLocation::Storage) + { + // stack layout: source_ref target_ref + pair const& offsets = sourceType.storageOffsetsOfMember(member.name); + m_context << offsets.first << Instruction::DUP3 << Instruction::ADD; + m_context << u256(offsets.second); + // stack: source_ref target_ref source_member_ref source_member_off + StorageItem(m_context, *sourceMemberType).retrieveValue(_location, true); + // stack: source_ref target_ref source_value... + } + else + { + solAssert(sourceType.location() == DataLocation::Memory, ""); + // stack layout: source_ref target_ref + m_context << sourceType.memoryOffsetOfMember(member.name); + m_context << Instruction::DUP3 << Instruction::ADD; + MemoryItem(m_context, *sourceMemberType).retrieveValue(_location, true); + // stack layout: source_ref target_ref source_value... + } + unsigned stackSize = sourceMemberType->sizeOnStack(); + pair const& offsets = structType.storageOffsetsOfMember(member.name); + m_context << dupInstruction(1 + stackSize) << offsets.first << Instruction::ADD; + m_context << u256(offsets.second); + // stack: source_ref target_ref target_off source_value... target_member_ref target_member_byte_off + StorageItem(m_context, *memberType).storeValue(*sourceMemberType, _location, true); + } + } + // stack layout: source_ref target_ref + solAssert(sourceType.sizeOnStack() == 1, "Unexpected source size."); + if (_move) + utils.popStackSlots(2); + else + m_context << Instruction::SWAP1 << Instruction::POP; + } + else + BOOST_THROW_EXCEPTION( + InternalCompilerError() + << errinfo_sourceLocation(_location) + << util::errinfo_comment("Invalid non-value type for assignment.")); + } } } diff --git a/libsolidity/codegen/YulUtilFunctions.cpp b/libsolidity/codegen/YulUtilFunctions.cpp index b66189f05..56d43e5f7 100644 --- a/libsolidity/codegen/YulUtilFunctions.cpp +++ b/libsolidity/codegen/YulUtilFunctions.cpp @@ -31,6 +31,8 @@ #include #include +#include + using namespace std; using namespace solidity; using namespace solidity::util; @@ -1926,6 +1928,78 @@ string YulUtilFunctions::copyArrayToStorageFunction(ArrayType const& _fromType, }); } +string YulUtilFunctions::copyInlineArrayToStorageFunction(InlineArrayType const& _fromType, ArrayType const& _toType) +{ + if (!_toType.isDynamicallySized()) + solAssert(_fromType.components().size() <= _toType.length(), ""); + + string const functionName = "copy_inline_array_to_storage_from_" + _fromType.identifier() + "_to_" + _toType.identifier(); + + return m_functionCollector.createFunction(functionName, [&](){ + + vector> memberSetValues; + unsigned stackItemIndex = 0; + for (Type const* type: _fromType.components()) + { + memberSetValues.emplace_back(); + memberSetValues.back()["setMember"] = Whiskers(R"({ + (elementSlot, elementOffset) + + + elementOffset := add(elementOffset, ) + if gt(elementOffset, sub(32, )) { + elementOffset := 0 + elementSlot := add(elementSlot, 1) + } + + elementSlot := add(elementSlot, ) + + })") + ("value", _fromType.sizeOnStack() ? + ", " + suffixedVariableNameList("var_", stackItemIndex, stackItemIndex + type->sizeOnStack()) : "") + ("multipleItemsPerSlot", _toType.storageStride() <= 16) + ("storageStride", to_string(_toType.storageStride())) + ("storageSize", _toType.baseType()->storageSize().str()) + ("updateStorageValue", updateStorageValueFunction(*type, *_toType.baseType())) + .render(); + + stackItemIndex += type->sizeOnStack(); + } + + Whiskers templ(R"( + function (slot) { + let length := + (slot, length) + + let elementSlot := (slot) + let elementOffset := 0 + + <#member> + + + + + if gt(elementOffset, 0) { + (elementSlot, elementOffset) + } + + } + )"); + if (_fromType.dataStoredIn(DataLocation::Storage)) + solAssert(!_fromType.isValueType(), ""); + templ("functionName", functionName); + templ("values", _fromType.sizeOnStack() ? + ", " + suffixedVariableNameList("var_", 0, _fromType.sizeOnStack()) : ""); + templ("arrayLength", to_string(_fromType.components().size())); + templ("resizeArray", resizeArrayFunction(_toType)); + templ("dstDataLocation", arrayDataAreaFunction(_toType)); + templ("member", move(memberSetValues)); + templ("multipleItemsPerSlot", _toType.storageStride() <= 16); + templ("partialClearStorageSlotFunction", partialClearStorageSlotFunction()); + + return templ.render(); + }); +} string YulUtilFunctions::copyByteArrayToStorageFunction(ArrayType const& _fromType, ArrayType const& _toType) { @@ -2820,6 +2894,31 @@ string YulUtilFunctions::updateStorageValueFunction( auto const* fromReferenceType = dynamic_cast(&_fromType); solAssert(toReferenceType, ""); + Whiskers templ(R"( + function (slot,offset ) { + if offset { () } + (slot) + } + )"); + templ("functionName", functionName); + templ("dynamicOffset", !_offset.has_value()); + templ("panic", panicFunction(PanicCode::Generic)); + + if (_fromType.category() == Type::Category::InlineArray) + { + solAssert(_toType.category() == Type::Category::Array, ""); + solAssert(!dynamic_cast(*toReferenceType).isByteArrayOrString(), ""); + + templ("extraParams", _fromType.sizeOnStack() ? + ", " + suffixedVariableNameList("value_", 0, _fromType.sizeOnStack()) : ""); + templ("copyToStorage", copyInlineArrayToStorageFunction( + dynamic_cast(_fromType), + dynamic_cast(_toType) + )); + + return templ.render(); + } + if (!fromReferenceType) { solAssert(_fromType.category() == Type::Category::StringLiteral, ""); @@ -2827,17 +2926,10 @@ string YulUtilFunctions::updateStorageValueFunction( auto const& toArrayType = dynamic_cast(*toReferenceType); solAssert(toArrayType.isByteArrayOrString(), ""); - return Whiskers(R"( - function (slot, offset) { - if offset { () } - (slot) - } - )") - ("functionName", functionName) - ("dynamicOffset", !_offset.has_value()) - ("panic", panicFunction(PanicCode::Generic)) - ("copyToStorage", copyLiteralToStorageFunction(dynamic_cast(_fromType).value())) - .render(); + templ("extraParams", ""); + templ("copyToStorage", copyLiteralToStorageFunction(dynamic_cast(_fromType).value())); + + return templ.render(); } solAssert(*toReferenceType->copyForLocation( @@ -2851,16 +2943,7 @@ string YulUtilFunctions::updateStorageValueFunction( solAssert(toReferenceType->category() == fromReferenceType->category(), ""); solAssert(_offset.value_or(0) == 0, ""); - Whiskers templ(R"( - function (slot, offset, ) { - if offset { () } - (slot, ) - } - )"); - templ("functionName", functionName); - templ("dynamicOffset", !_offset.has_value()); - templ("panic", panicFunction(PanicCode::Generic)); - templ("value", suffixedVariableNameList("value_", 0, _fromType.sizeOnStack())); + templ("extraParams", ", " + suffixedVariableNameList("value_", 0, _fromType.sizeOnStack())); if (_fromType.category() == Type::Category::Array) templ("copyToStorage", copyArrayToStorageFunction( dynamic_cast(_fromType), @@ -3332,6 +3415,11 @@ string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to) solAssert(_to.category() == Type::Category::Array, ""); return arrayConversionFunction(fromArrayType, dynamic_cast(_to)); } + else if (_from.category() == Type::Category::InlineArray) + { + solAssert(_to.category() == Type::Category::Array, ""); + return inlineArrayConversionFunction(dynamic_cast(_from), dynamic_cast(_to)); + } if (_from.sizeOnStack() != 1 || _to.sizeOnStack() != 1) return conversionFunctionSpecial(_from, _to); @@ -3781,6 +3869,64 @@ string YulUtilFunctions::arrayConversionFunction(ArrayType const& _from, ArrayTy }); } +string YulUtilFunctions::inlineArrayConversionFunction(InlineArrayType const& _from, ArrayType const& _to) +{ + if (_to.dataStoredIn(DataLocation::CallData)) + solAssert(false); + + if (!_to.isDynamicallySized()) + solAssert(_to.length() == _from.components().size()); + + string functionName = + "convert_inline_array_" + + _from.identifier() + + "_to_" + + _to.identifier(); + + vector> memberSetValues; + unsigned stackItemIndex = 0; + + for (auto&& [index, type]: _from.components() | ranges::views::enumerate) + { + memberSetValues.emplace_back(); + memberSetValues.back()["setMember"] = Whiskers(R"( + let := () + (add(mpos, ), ) + )") + ("memberValues", suffixedVariableNameList("memberValue_", 0, _to.baseType()->stackItems().size())) + ("offset", to_string(0x20 * index)) + ("value", suffixedVariableNameList("var_", stackItemIndex, stackItemIndex + type->sizeOnStack())) + ("conversionFunction", conversionFunction(*type, *_to.baseType())) + ("writeToMemory", writeToMemoryFunction(*_to.baseType())) + .render(); + + stackItemIndex += type->sizeOnStack(); + } + + return m_functionCollector.createFunction(functionName, [&]() { + Whiskers templ(R"( + function () -> converted { + converted := () + let mpos := converted + mpos := add(mpos, 0x20) + <#member> + { + + } + + } + )"); + templ("functionName", functionName); + templ("allocateArray", allocateMemoryArrayFunction(_to)); + templ("length", toCompactHexWithPrefix(_from.components().size())); + templ("toDynamic", _to.isDynamicallySized()); + templ("values", suffixedVariableNameList("var_", 0, _from.sizeOnStack())); + templ("member", move(memberSetValues)); + + return templ.render(); + }); +} + string YulUtilFunctions::cleanupFunction(Type const& _type) { if (auto userDefinedValueType = dynamic_cast(&_type)) diff --git a/libsolidity/codegen/YulUtilFunctions.h b/libsolidity/codegen/YulUtilFunctions.h index 7d736df5b..a6a1c48e4 100644 --- a/libsolidity/codegen/YulUtilFunctions.h +++ b/libsolidity/codegen/YulUtilFunctions.h @@ -255,6 +255,9 @@ public: /// signature (to_slot, from_ptr) -> std::string copyArrayToStorageFunction(ArrayType const& _fromType, ArrayType const& _toType); + std::string copyInlineArrayToStorageFunction(InlineArrayType const& _fromType, ArrayType const& _toType); + + /// @returns the name of a function that will copy a byte array to storage /// signature (to_slot, from_ptr) -> std::string copyByteArrayToStorageFunction(ArrayType const& _fromType, ArrayType const& _toType); @@ -538,6 +541,8 @@ private: /// Special case of conversion functions - handles all array conversions. std::string arrayConversionFunction(ArrayType const& _from, ArrayType const& _to); + std::string inlineArrayConversionFunction(InlineArrayType const& _from, ArrayType const& _to); + /// Special case of conversionFunction - handles everything that does not /// use exactly one variable to hold the value. std::string conversionFunctionSpecial(Type const& _from, Type const& _to); diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index 5eaeed743..4ab44cb16 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -47,6 +47,7 @@ #include #include +#include #include using namespace std; @@ -492,75 +493,49 @@ bool IRGeneratorForStatements::visit(TupleExpression const& _tuple) { setLocation(_tuple); - if (_tuple.isInlineArray()) + bool willBeWrittenTo = _tuple.annotation().willBeWrittenTo; + if (willBeWrittenTo) + solAssert(!m_currentLValue); + if (!_tuple.isInlineArray() && _tuple.components().size() == 1) { - auto const& arrayType = dynamic_cast(*_tuple.annotation().type); - solAssert(!arrayType.isDynamicallySized(), "Cannot create dynamically sized inline array."); - define(_tuple) << - m_utils.allocateMemoryArrayFunction(arrayType) << - "(" << - _tuple.components().size() << - ")\n"; - - string mpos = IRVariable(_tuple).part("mpos").name(); - Type const& baseType = *arrayType.baseType(); - for (size_t i = 0; i < _tuple.components().size(); i++) - { - Expression const& component = *_tuple.components()[i]; - component.accept(*this); - setLocation(_tuple); - IRVariable converted = convert(component, baseType); - appendCode() << - m_utils.writeToMemoryFunction(baseType) << - "(" << - ("add(" + mpos + ", " + to_string(i * arrayType.memoryStride()) + ")") << - ", " << - converted.commaSeparatedList() << - ")\n"; - } + solAssert(_tuple.components().front()); + _tuple.components().front()->accept(*this); + setLocation(_tuple); + if (willBeWrittenTo) + solAssert(!!m_currentLValue); + else + define(_tuple, *_tuple.components().front()); } else { - bool willBeWrittenTo = _tuple.annotation().willBeWrittenTo; - if (willBeWrittenTo) - solAssert(!m_currentLValue); - if (_tuple.components().size() == 1) - { - solAssert(_tuple.components().front()); - _tuple.components().front()->accept(*this); - setLocation(_tuple); - if (willBeWrittenTo) - solAssert(!!m_currentLValue); - else - define(_tuple, *_tuple.components().front()); - } - else - { - vector> lvalues; - for (size_t i = 0; i < _tuple.components().size(); ++i) - if (auto const& component = _tuple.components()[i]) - { - component->accept(*this); - setLocation(_tuple); - if (willBeWrittenTo) - { - solAssert(!!m_currentLValue); - lvalues.emplace_back(std::move(m_currentLValue)); - m_currentLValue.reset(); - } - else - define(IRVariable(_tuple).tupleComponent(i), *component); - } - else if (willBeWrittenTo) - lvalues.emplace_back(); + vector> lvalues; - if (_tuple.annotation().willBeWrittenTo) - m_currentLValue.emplace(IRLValue{ - *_tuple.annotation().type, - IRLValue::Tuple{std::move(lvalues)} - }); + for (auto&& [index, component]: _tuple.components() | ranges::views::enumerate) + { + if (component) + { + component->accept(*this); + setLocation(_tuple); + if (willBeWrittenTo) + { + solAssert(!!m_currentLValue); + lvalues.emplace_back(std::move(m_currentLValue)); + m_currentLValue.reset(); + } + else + define(IRVariable(_tuple).tupleComponent(index), *component); + } + else if (willBeWrittenTo) + lvalues.emplace_back(); } + + if (_tuple.annotation().willBeWrittenTo) + m_currentLValue.emplace(IRLValue{ + *_tuple.annotation().type, + IRLValue::Tuple{std::move(lvalues)} + }); } + return false; } @@ -2284,6 +2259,29 @@ void IRGeneratorForStatements::endVisit(IndexAccess const& _indexAccess) } } } + else if (baseType.category() == Type::Category::InlineArray) + { + InlineArrayType const& inlineArrayType = dynamic_cast(baseType); + + ArrayType const* arrayType = dynamic_cast(inlineArrayType.mobileType()); + solAssert(arrayType); + + IRVariable irArray = convert(IRVariable(_indexAccess.baseExpression()), *arrayType); + + string const memAddress = + m_utils.memoryArrayIndexAccessFunction(*arrayType) + + "(" + + irArray.part("mpos").name() + + ", " + + expressionAsType(*_indexAccess.indexExpression(), *TypeProvider::uint256()) + + ")"; + + setLValue(_indexAccess, IRLValue{ + *arrayType->baseType(), + IRLValue::Memory{memAddress} + }); + } + else if (baseType.category() == Type::Category::FixedBytes) { auto const& fixedBytesType = dynamic_cast(baseType); @@ -2532,7 +2530,7 @@ void IRGeneratorForStatements::appendExternalFunctionCall( } // NOTE: When the expected size of returndata is static, we pass that in to the call opcode and it gets copied automatically. - // When it's dynamic, we get zero from estimatedReturnSize() instead and then we need an explicit returndatacopy(). + // When it's dynamic, we get zero from estimatedReturnSize() instead and then we need an explicit returndatacopy(). Whiskers templ(R"( if iszero(extcodesize(
)) { () } @@ -3024,6 +3022,12 @@ void IRGeneratorForStatements::writeToLValue(IRLValue const& _lvalue, IRVariable m_utils.copyLiteralToMemoryFunction(literalType->value()) + "()" << ")\n"; } + else if (dynamic_cast(&_value.type())) + { + solAssert(dynamic_cast(&_lvalue.type)); + IRVariable value = convert(_value, _lvalue.type); + writeToLValue(_lvalue, value); + } else { solAssert(_lvalue.type.sizeOnStack() == 1); diff --git a/libsolidity/codegen/ir/IRVariable.cpp b/libsolidity/codegen/ir/IRVariable.cpp index 3d5f2f5a4..862234880 100644 --- a/libsolidity/codegen/ir/IRVariable.cpp +++ b/libsolidity/codegen/ir/IRVariable.cpp @@ -102,7 +102,7 @@ string IRVariable::name() const IRVariable IRVariable::tupleComponent(size_t _i) const { solAssert( - m_type.category() == Type::Category::Tuple, + m_type.category() == Type::Category::Tuple || m_type.category() == Type::Category::InlineArray, "Requested tuple component of non-tuple IR variable." ); return part(IRNames::tupleComponent(_i)); diff --git a/libsolidity/formal/SymbolicTypes.cpp b/libsolidity/formal/SymbolicTypes.cpp index e40ff9245..3151a413e 100644 --- a/libsolidity/formal/SymbolicTypes.cpp +++ b/libsolidity/formal/SymbolicTypes.cpp @@ -78,6 +78,10 @@ SortPointer smtSort(frontend::Type const& _type) solAssert(stringLitType, ""); array = make_shared(SortProvider::uintSort, SortProvider::uintSort); } + else if (auto const* inlineArrayType = dynamic_cast(&_type)) + { + array = make_shared(SortProvider::uintSort, smtSortAbstractFunction(*inlineArrayType->componentsCommonMobileType())); + } else { frontend::ArrayType const* arrayType = nullptr; @@ -93,8 +97,19 @@ SortPointer smtSort(frontend::Type const& _type) } string tupleName; - auto sliceArrayType = dynamic_cast(&_type); - ArrayType const* arrayType = sliceArrayType ? &sliceArrayType->arrayType() : dynamic_cast(&_type); + + ArrayType const* arrayType = nullptr; + if (auto const* inlineArrayType = dynamic_cast(&_type)) + arrayType = TypeProvider::array( + DataLocation::Memory, + inlineArrayType->componentsCommonMobileType(), + inlineArrayType->components().size() + ); + else if (auto const* sliceArrayType = dynamic_cast(&_type)) + arrayType = &sliceArrayType->arrayType(); + else + arrayType = dynamic_cast(&_type); + if ( (arrayType && arrayType->isByteArrayOrString()) || _type.category() == frontend::Type::Category::StringLiteral @@ -371,7 +386,8 @@ bool isArray(frontend::Type const& _type) { return _type.category() == frontend::Type::Category::Array || _type.category() == frontend::Type::Category::StringLiteral || - _type.category() == frontend::Type::Category::ArraySlice; + _type.category() == frontend::Type::Category::ArraySlice || + _type.category() == frontend::Type::Category::InlineArray; } bool isTuple(frontend::Type const& _type) @@ -475,6 +491,11 @@ smtutil::Expression zeroValue(frontend::Type const* _type) if (!arrayType->isDynamicallySized()) length = bigint(arrayType->length()); } + else if (auto inlineArrayType = dynamic_cast(_type)) + { + zeroArray = smtutil::Expression::const_array(smtutil::Expression(sortSort), zeroValue(inlineArrayType->componentsCommonMobileType())); + length = bigint(inlineArrayType->components().size()); + } else if (auto mappingType = dynamic_cast(_type)) zeroArray = smtutil::Expression::const_array(smtutil::Expression(sortSort), zeroValue(mappingType->valueType())); else diff --git a/scripts/test_antlr_grammar.sh b/scripts/test_antlr_grammar.sh index 5745f01be..ba41ab9e9 100755 --- a/scripts/test_antlr_grammar.sh +++ b/scripts/test_antlr_grammar.sh @@ -126,7 +126,9 @@ done < <( grep -v -E 'license/license_hidden_unicode.sol' | grep -v -E 'license/license_unicode.sol' | # Skipping tests with 'something.address' as 'address' as the grammar fails on those - grep -v -E 'inlineAssembly/external_function_pointer_address.*.sol' + grep -v -E 'inlineAssembly/external_function_pointer_address.*.sol' | + # Skipping a test with an empty array as the grammar fails on it + grep -v -E 'array/inline_array_return_dynamic.sol' ) YUL_FILES=() diff --git a/test/libsolidity/semanticTests/abiEncoderV2/abi_encode_v2_in_function_inherited_in_v1_contract.sol b/test/libsolidity/semanticTests/abiEncoderV2/abi_encode_v2_in_function_inherited_in_v1_contract.sol index e0ab2c97f..97111c093 100644 --- a/test/libsolidity/semanticTests/abiEncoderV2/abi_encode_v2_in_function_inherited_in_v1_contract.sol +++ b/test/libsolidity/semanticTests/abiEncoderV2/abi_encode_v2_in_function_inherited_in_v1_contract.sol @@ -30,6 +30,6 @@ contract C is B { } // ---- // test() -> 77 -// gas irOptimized: 120170 -// gas legacy: 155093 -// gas legacyOptimized: 111550 +// gas irOptimized: 115552 +// gas legacy: 159144 +// gas legacyOptimized: 110758 diff --git a/test/libsolidity/semanticTests/array/arrays_complex_from_and_to_storage.sol b/test/libsolidity/semanticTests/array/arrays_complex_from_and_to_storage.sol index 17243226f..7d855bc49 100644 --- a/test/libsolidity/semanticTests/array/arrays_complex_from_and_to_storage.sol +++ b/test/libsolidity/semanticTests/array/arrays_complex_from_and_to_storage.sol @@ -13,8 +13,8 @@ contract Test { // ---- // set(uint24[3][]): 0x20, 0x06, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12 -> 0x06 // gas irOptimized: 186766 -// gas legacy: 211149 -// gas legacyOptimized: 206054 +// gas legacy: 211155 +// gas legacyOptimized: 206060 // data(uint256,uint256): 0x02, 0x02 -> 0x09 // data(uint256,uint256): 0x05, 0x01 -> 0x11 // data(uint256,uint256): 0x06, 0x00 -> FAILURE diff --git a/test/libsolidity/semanticTests/array/copying/array_copy_calldata_storage.sol b/test/libsolidity/semanticTests/array/copying/array_copy_calldata_storage.sol index 6afd8f88f..e933f0433 100644 --- a/test/libsolidity/semanticTests/array/copying/array_copy_calldata_storage.sol +++ b/test/libsolidity/semanticTests/array/copying/array_copy_calldata_storage.sol @@ -21,6 +21,6 @@ contract c { // ---- // store(uint256[9],uint8[3][]): 21, 22, 23, 24, 25, 26, 27, 28, 29, 0x140, 4, 1, 2, 3, 11, 12, 13, 21, 22, 23, 31, 32, 33 -> 32 // gas irOptimized: 648324 -// gas legacy: 694515 -// gas legacyOptimized: 694013 +// gas legacy: 694519 +// gas legacyOptimized: 694017 // retrieve() -> 9, 28, 9, 28, 4, 3, 32 diff --git a/test/libsolidity/semanticTests/array/copying/array_copy_cleanup_uint40.sol b/test/libsolidity/semanticTests/array/copying/array_copy_cleanup_uint40.sol index 915c0aeed..b6e2b9fa7 100644 --- a/test/libsolidity/semanticTests/array/copying/array_copy_cleanup_uint40.sol +++ b/test/libsolidity/semanticTests/array/copying/array_copy_cleanup_uint40.sol @@ -47,5 +47,5 @@ contract C { // ---- // f() -> true // gas irOptimized: 146936 -// gas legacy: 155961 -// gas legacyOptimized: 153588 +// gas legacy: 155962 +// gas legacyOptimized: 153589 diff --git a/test/libsolidity/semanticTests/array/copying/array_copy_different_packing.sol b/test/libsolidity/semanticTests/array/copying/array_copy_different_packing.sol index 86da315c3..a9b8f294c 100644 --- a/test/libsolidity/semanticTests/array/copying/array_copy_different_packing.sol +++ b/test/libsolidity/semanticTests/array/copying/array_copy_different_packing.sol @@ -20,5 +20,5 @@ contract c { // ---- // test() -> 0x01000000000000000000000000000000000000000000000000, 0x02000000000000000000000000000000000000000000000000, 0x03000000000000000000000000000000000000000000000000, 0x04000000000000000000000000000000000000000000000000, 0x05000000000000000000000000000000000000000000000000 // gas irOptimized: 208149 -// gas legacy: 221856 -// gas legacyOptimized: 220680 +// gas legacy: 221858 +// gas legacyOptimized: 220682 diff --git a/test/libsolidity/semanticTests/array/copying/array_copy_storage_storage_different_base_nested.sol b/test/libsolidity/semanticTests/array/copying/array_copy_storage_storage_different_base_nested.sol index 3e59244bd..6829a69e9 100644 --- a/test/libsolidity/semanticTests/array/copying/array_copy_storage_storage_different_base_nested.sol +++ b/test/libsolidity/semanticTests/array/copying/array_copy_storage_storage_different_base_nested.sol @@ -24,5 +24,5 @@ contract c { // ---- // test() -> 3, 4 // gas irOptimized: 189690 -// gas legacy: 195353 -// gas legacyOptimized: 192441 +// gas legacy: 195355 +// gas legacyOptimized: 192443 diff --git a/test/libsolidity/semanticTests/array/copying/array_copy_target_leftover.sol b/test/libsolidity/semanticTests/array/copying/array_copy_target_leftover.sol index f2d02ee57..15b155f40 100644 --- a/test/libsolidity/semanticTests/array/copying/array_copy_target_leftover.sol +++ b/test/libsolidity/semanticTests/array/copying/array_copy_target_leftover.sol @@ -21,4 +21,4 @@ contract c { // test() -> 0xffffffff, 0x0000000000000000000000000a00090008000700060005000400030002000100, 0x0000000000000000000000000000000000000000000000000000000000000000 // gas irOptimized: 124910 // gas legacy: 187414 -// gas legacyOptimized: 165659 +// gas legacyOptimized: 165660 diff --git a/test/libsolidity/semanticTests/array/copying/arrays_from_and_to_storage.sol b/test/libsolidity/semanticTests/array/copying/arrays_from_and_to_storage.sol index d13381d80..94aa6e39d 100644 --- a/test/libsolidity/semanticTests/array/copying/arrays_from_and_to_storage.sol +++ b/test/libsolidity/semanticTests/array/copying/arrays_from_and_to_storage.sol @@ -11,8 +11,8 @@ contract Test { // ---- // set(uint24[]): 0x20, 18, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 -> 18 // gas irOptimized: 99616 -// gas legacy: 103563 -// gas legacyOptimized: 101397 +// gas legacy: 103564 +// gas legacyOptimized: 101398 // data(uint256): 7 -> 8 // data(uint256): 15 -> 16 // data(uint256): 18 -> FAILURE diff --git a/test/libsolidity/semanticTests/array/copying/cleanup_during_multi_element_per_slot_copy.sol b/test/libsolidity/semanticTests/array/copying/cleanup_during_multi_element_per_slot_copy.sol index 999d49eaa..218373cb4 100644 --- a/test/libsolidity/semanticTests/array/copying/cleanup_during_multi_element_per_slot_copy.sol +++ b/test/libsolidity/semanticTests/array/copying/cleanup_during_multi_element_per_slot_copy.sol @@ -16,7 +16,7 @@ contract C { } // ---- // constructor() -// gas irOptimized: 237351 -// gas legacy: 221315 -// gas legacyOptimized: 185247 +// gas irOptimized: 192200 +// gas legacy: 237108 +// gas legacyOptimized: 190689 // f() -> 0 diff --git a/test/libsolidity/semanticTests/array/copying/copy_function_internal_storage_array.sol b/test/libsolidity/semanticTests/array/copying/copy_function_internal_storage_array.sol index e938b03d6..a6fdcc555 100644 --- a/test/libsolidity/semanticTests/array/copying/copy_function_internal_storage_array.sol +++ b/test/libsolidity/semanticTests/array/copying/copy_function_internal_storage_array.sol @@ -17,5 +17,5 @@ contract C { // ---- // test() -> 7 // gas irOptimized: 122483 -// gas legacy: 205196 -// gas legacyOptimized: 204987 +// gas legacy: 205197 +// gas legacyOptimized: 204988 diff --git a/test/libsolidity/semanticTests/array/copying/copy_internal_function_array_to_storage.sol b/test/libsolidity/semanticTests/array/copying/copy_internal_function_array_to_storage.sol index b13d15e17..20f94a619 100644 --- a/test/libsolidity/semanticTests/array/copying/copy_internal_function_array_to_storage.sol +++ b/test/libsolidity/semanticTests/array/copying/copy_internal_function_array_to_storage.sol @@ -21,6 +21,6 @@ contract C { // compileToEwasm: also // ---- // one() -> 3 -// gas legacy: 140260 -// gas legacyOptimized: 140097 +// gas legacy: 140261 +// gas legacyOptimized: 140098 // two() -> FAILURE, hex"4e487b71", 0x51 diff --git a/test/libsolidity/semanticTests/array/inline_array_assignment_to_memory_array.sol b/test/libsolidity/semanticTests/array/inline_array_assignment_to_memory_array.sol new file mode 100644 index 000000000..1a3557ec1 --- /dev/null +++ b/test/libsolidity/semanticTests/array/inline_array_assignment_to_memory_array.sol @@ -0,0 +1,18 @@ +contract C { + function f() public returns (uint8[3] memory) { + uint8[3][] memory x = new uint8[3][](1); + x[0] = [1, 2, 3]; + return x[0]; + } + + function g() public returns (uint8[] memory) { + uint8[][] memory x = new uint8[][](1); + x[0] = [4, 5, 6]; + return x[0]; + } +} +// ==== +// compileViaYul: also +// ---- +// f() -> 1, 2, 3 +// g() -> 0x20, 3, 4, 5, 6 diff --git a/test/libsolidity/semanticTests/array/inline_array_assignment_to_storage_array.sol b/test/libsolidity/semanticTests/array/inline_array_assignment_to_storage_array.sol new file mode 100644 index 000000000..1bb356d60 --- /dev/null +++ b/test/libsolidity/semanticTests/array/inline_array_assignment_to_storage_array.sol @@ -0,0 +1,25 @@ +contract C { + uint8[3][] x; + uint8[][] y; + + constructor() { + x = new uint8[3][](1); + x[0] = [1, 2, 3]; + + y = new uint8[][](1); + y[0] = [4, 5, 6]; + } + + function f() public returns (uint8[3] memory) { + return x[0]; + } + + function g() public returns (uint8[] memory) { + return y[0]; + } +} +// ==== +// compileViaYul: also +// ---- +// f() -> 1, 2, 3 +// g() -> 0x20, 3, 4, 5, 6 diff --git a/test/libsolidity/semanticTests/array/inline_array_conversion.sol b/test/libsolidity/semanticTests/array/inline_array_conversion.sol new file mode 100644 index 000000000..f27b8a466 --- /dev/null +++ b/test/libsolidity/semanticTests/array/inline_array_conversion.sol @@ -0,0 +1,15 @@ +pragma abicoder v2; + +contract C { + string sString = "text2"; + + function f(string calldata cString) public returns (string[] memory) { + string memory mString = "text3"; + return ["", "text1", sString, mString, cString]; + } +} + +// ==== +// compileViaYul: true +// ---- +// f(string): 0x20, 5, "text4" -> 0x20, 5, 0xa0, 0xc0, 0x0100, 0x0140, 0x0180, 0, 5, 52647573331277248417481526129911949709942841133814091230869433742303074189312, 5, 52647573331382560709150083316609867737626511566132986326269982853557385166848, 5, 52647573331487873000818640503307785765310181998451881421670531964811696144384, 5, 52647573331593185292487197690005703792993852430770776517071081076066007121920 diff --git a/test/libsolidity/semanticTests/array/inline_array_return_dynamic.sol b/test/libsolidity/semanticTests/array/inline_array_return_dynamic.sol new file mode 100644 index 000000000..9f2d0658f --- /dev/null +++ b/test/libsolidity/semanticTests/array/inline_array_return_dynamic.sol @@ -0,0 +1,27 @@ +pragma abicoder v2; + +contract C { + function f() public returns (uint8[] memory) { + return [1, 2, 3, 4, 5]; + } + + function g() public returns (uint8[][] memory) { + return [[1, 2], [3], []]; + } + + function h() public returns (uint8[][3] memory) { + return [[1, 2], [3], []]; + } + + function i() public returns (uint8[2][] memory) { + return [[1, 2], [3,4], [5,6]]; + } +} + +// ==== +// compileViaYul: true +// ---- +// f() -> 0x20, 5, 1, 2, 3, 4, 5 +// g() -> 0x20, 3, 0x60, 0xc0, 0x0100, 2, 1, 2, 1, 3, 0 +// h() -> 0x20, 0x60, 0xc0, 0x0100, 2, 1, 2, 1, 3, 0 +// i() -> 0x20, 3, 1, 2, 3, 4, 5, 6 diff --git a/test/libsolidity/semanticTests/array/inline_array_to_memory_array_conversion.sol b/test/libsolidity/semanticTests/array/inline_array_to_memory_array_conversion.sol new file mode 100644 index 000000000..4bbe08d8f --- /dev/null +++ b/test/libsolidity/semanticTests/array/inline_array_to_memory_array_conversion.sol @@ -0,0 +1,63 @@ +pragma abicoder v2; + +contract C { + + struct CustomStruct{ + uint16 index; + string text; + } + + function statically_sized_uint_array() public returns (uint8[3] memory) { + uint8[3] memory a = [1, 2, 3]; + return a; + } + + function dynamically_sized_uint_array() public returns (uint8[] memory) { + uint8[] memory a = [1, 2, 3]; + return a; + } + + function statically_sized_int_array() public returns (int8[3] memory) { + int8[3] memory a = [-1, -2, -3]; + return a; + } + + function dynamically_sized_int_array() public returns (int8[] memory) { + int8[] memory a = [-1, -2, -3]; + return a; + } + + function statically_sized_string_array() public returns (string[2] memory) { + string[2] memory a = ["foo", "bar"]; + return a; + } + + function dynamically_sized_string_array() public returns (string[] memory) { + string[] memory a = ["foo", "bar"]; + return a; + } + + function statically_sized_struct_array() public returns (CustomStruct[2] memory) { + CustomStruct[2] memory s = [CustomStruct(1, "foo"), CustomStruct(2, "bar")]; + return s; + } + + function dynamically_sized_struct_array() public returns (CustomStruct[] memory) { + CustomStruct[] memory s = [CustomStruct(1, "foo"), CustomStruct(2, "bar")]; + return s; + } +} + +// ==== +// EVMVersion: >=constantinople +// compileToEwasm: also +// compileViaYul: also +// ---- +// statically_sized_uint_array() -> 1, 2, 3 +// dynamically_sized_uint_array() -> 0x20, 3, 1, 2, 3 +// statically_sized_int_array() -> -1, -2, -3 +// dynamically_sized_int_array() -> 0x20, 3, -1, -2, -3 +// statically_sized_string_array() -> 0x20, 0x40, 0x80, 3, "foo", 3, "bar" +// dynamically_sized_string_array() -> 0x20, 2, 0x40, 0x80, 3, 46332796673528066027243215619882264990369332300865266851730502456685210107904, 3, 44498830125527143464827115118378702402016761369235290884359940707316142178304 +// statically_sized_struct_array() -> 0x20, 0x40, 0xc0, 1, 0x40, 3, 46332796673528066027243215619882264990369332300865266851730502456685210107904, 2, 0x40, 3, 44498830125527143464827115118378702402016761369235290884359940707316142178304 +// dynamically_sized_struct_array() -> 0x20, 2, 0x40, 0xc0, 1, 0x40, 3, 46332796673528066027243215619882264990369332300865266851730502456685210107904, 2, 0x40, 3, 44498830125527143464827115118378702402016761369235290884359940707316142178304 diff --git a/test/libsolidity/semanticTests/array/inline_array_to_storage_assignment.sol b/test/libsolidity/semanticTests/array/inline_array_to_storage_assignment.sol new file mode 100644 index 000000000..a7b400599 --- /dev/null +++ b/test/libsolidity/semanticTests/array/inline_array_to_storage_assignment.sol @@ -0,0 +1,20 @@ +contract C { + uint8[3] st = [1, 2, 3]; + uint8[] public dt = [4, 5, 6]; + + function s() public returns (uint8[3] memory) { + return st; + } + + function d() public returns (uint8[] memory) { + return dt; + } +} +// ==== +// EVMVersion: >=constantinople +// compileToEwasm: also +// compileViaYul: also +// ---- +// s() -> 1, 2, 3 +// d() -> 0x20, 3, 4, 5, 6 + diff --git a/test/libsolidity/semanticTests/array/inline_array_to_two_dimensional_storage_array.sol b/test/libsolidity/semanticTests/array/inline_array_to_two_dimensional_storage_array.sol new file mode 100644 index 000000000..cf9f1b007 --- /dev/null +++ b/test/libsolidity/semanticTests/array/inline_array_to_two_dimensional_storage_array.sol @@ -0,0 +1,20 @@ +pragma abicoder v2; + +contract C { + uint8[3][2] st = [[1, 2, 3], [4, 5, 6]]; + uint8[][] dt = [[1, 2], [3, 4, 5, 6]]; + + function s() public returns (uint8[3][2] memory) { + return st; + } + + function d() public returns (uint8[][] memory) { + return dt; + } +} + +// ==== +// compileViaYul: true +// ---- +// s() -> 1, 2, 3, 4, 5, 6 +// d() -> 0x20, 2, 0x40, 0xa0, 2, 1, 2, 4, 3, 4, 5, 6 diff --git a/test/libsolidity/semanticTests/array/inline_array_with_utf8_sequence.sol b/test/libsolidity/semanticTests/array/inline_array_with_utf8_sequence.sol new file mode 100644 index 000000000..b04150251 --- /dev/null +++ b/test/libsolidity/semanticTests/array/inline_array_with_utf8_sequence.sol @@ -0,0 +1,11 @@ +pragma abicoder v2; + +contract C { + function f() external pure returns (string[2] memory rdatas) { + rdatas = [hex'74000001', hex'c4a40001']; + } +} +// ==== +// compileViaYul: also +// ---- +// f() -> 0x20, 0x40, 0x80, 4, "t\0\0\x01", 4, "\xc4\xa4\0\x01" diff --git a/test/libsolidity/semanticTests/array/inline_bytes_array_to_storage_assignment.sol b/test/libsolidity/semanticTests/array/inline_bytes_array_to_storage_assignment.sol new file mode 100644 index 000000000..414a6d849 --- /dev/null +++ b/test/libsolidity/semanticTests/array/inline_bytes_array_to_storage_assignment.sol @@ -0,0 +1,12 @@ +contract C { + bytes[2] public staticBytesArray = [hex"616263", hex"646566"]; + bytes[] public dynamicBytesArray = [hex"616263", hex"646566"]; +} + +// ==== +// compileViaYul: also +// ---- +// staticBytesArray(uint256): 0 -> 0x20, 3, 0x6162630000000000000000000000000000000000000000000000000000000000 +// staticBytesArray(uint256): 1 -> 0x20, 3, 0x6465660000000000000000000000000000000000000000000000000000000000 +// dynamicBytesArray(uint256): 0 -> 0x20, 3, 0x6162630000000000000000000000000000000000000000000000000000000000 +// dynamicBytesArray(uint256): 1 -> 0x20, 3, 0x6465660000000000000000000000000000000000000000000000000000000000 diff --git a/test/libsolidity/semanticTests/array/inline_string_array_to_storage_assignment.sol b/test/libsolidity/semanticTests/array/inline_string_array_to_storage_assignment.sol new file mode 100644 index 000000000..92dbd938e --- /dev/null +++ b/test/libsolidity/semanticTests/array/inline_string_array_to_storage_assignment.sol @@ -0,0 +1,16 @@ +contract C { + string[4] public staticArray = ["foo", "", "a very long string that needs more than 32 bytes", "bar"]; + string[] public dynamicArray = ["foo", "", "a very long string that needs more than 32 bytes", "bar"]; +} + +// ==== +// compileViaYul: also +// ---- +// staticArray(uint256): 0 -> 0x20, 3, "foo" +// staticArray(uint256): 1 -> 0x20, 0 +// staticArray(uint256): 2 -> 0x20, 0x30, 0x612076657279206c6f6e6720737472696e672074686174206e65656473206d6f, 0x7265207468616E20333220627974657300000000000000000000000000000000 +// staticArray(uint256): 3 -> 0x20, 3, "bar" +// dynamicArray(uint256): 0 -> 0x20, 3, "foo" +// dynamicArray(uint256): 1 -> 0x20, 0 +// dynamicArray(uint256): 2 -> 0x20, 0x30, 0x612076657279206c6f6e6720737472696e672074686174206e65656473206d6f, 0x7265207468616E20333220627974657300000000000000000000000000000000 +// dynamicArray(uint256): 3 -> 0x20, 3, "bar" diff --git a/test/libsolidity/semanticTests/array/multi_dimension_inline_array_to_memory_array_assignment.sol b/test/libsolidity/semanticTests/array/multi_dimension_inline_array_to_memory_array_assignment.sol new file mode 100644 index 000000000..a291221f0 --- /dev/null +++ b/test/libsolidity/semanticTests/array/multi_dimension_inline_array_to_memory_array_assignment.sol @@ -0,0 +1,39 @@ +pragma abicoder v2; + +contract C { + + function test1() public returns (uint8[1][] memory) { + uint8[1][] memory a = [[1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [11], [12], [13], [14], [15], [16]]; + return a; + } + + function test2() public returns (int8[2][] memory) { + int8[2][] memory a = [[-1, -2], [-3, -4], [-5, -6]]; + return a; + } + + function test3() public returns (uint16[][2] memory) { + uint16[][2] memory a = [[1, 2, 3],[4, 5, 6, 7, 8, 9, 10, 11, 12, 13]]; + return a; + } + + function test4() public returns (uint8[][2][] memory) { + uint8[][2][] memory a = [[[1, 2, 3], [4, 5]], [[6], [7]], [[8, 9, 10, 11], [12, 13, 14, 15, 16]]]; + return a; + } + + function test5() public returns (string[2][] memory) { + string[2][] memory a = [["this", "is"], ["just", "a"], ["test", "array"]]; + return a; + } + +} + +// ==== +// compileViaYul: also +// ---- +// test1() -> 0x20, 0x10, 0x01, 0x02, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0x10 +// test2() -> 0x20, 0x03, -1, -2, -3, -4, -5, -6 +// test3() -> 0x20, 0x40, 0xc0, 0x03, 1, 2, 3, 10, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13 +// test4() -> 0x20, 0x03, 0x60, 0x0180, 0x0240, 0x40, 0xc0, 3, 1, 2, 3, 2, 4, 5, 0x40, 0x80, 1, 6, 1, 7, 0x40, 0xe0, 4, 8, 9, 10, 11, 5, 12, 13, 14, 15, 0x10 +// test5() -> 0x20, 3, 0x60, 288, 0x01e0, 0x40, 0x80, 4, 52652770314156132753103522558211245866589054961607131434777992190478746910720, 2, 47696036513692484977101116032555085334762927796157333774492759403894270853120, 0x40, 0x80, 4, 48152679884589002438443377966044670264050608660991796074848385302132292583424, 1, 43874346312576839672212443538448152585028080127215369968075725190498334277632, 0x40, 0x80, 4, 52647538817385212172903286807934654968315727694643370704309751478220717293568, 5, 44076556304902723615564186688849689667362290371271333483270849954029028507648 diff --git a/test/libsolidity/semanticTests/array/multi_dimension_inline_array_to_storage_array_assignment.sol b/test/libsolidity/semanticTests/array/multi_dimension_inline_array_to_storage_array_assignment.sol new file mode 100644 index 000000000..15cfdd2f9 --- /dev/null +++ b/test/libsolidity/semanticTests/array/multi_dimension_inline_array_to_storage_array_assignment.sol @@ -0,0 +1,38 @@ +pragma abicoder v2; + +contract C { + uint8[1][] array1 = [[1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [11], [12], [13], [14], [15], [16]]; + int8[2][] array2 = [[1, 2], [3, 4], [5, 6]]; + uint16[][2] array3 = [[1, 2, 3],[4, 5, 6, 7, 8, 9, 10, 11, 12, 13]]; + uint8[][2][] array4 = [[[1, 2, 3], [4, 5]], [[6], [7]], [[8, 9, 10, 11], [12, 13, 14, 15, 16]]]; + string[2][] array5 = [["this", "is"], ["just", "a"], ["test", "array"]]; + + function test1() public returns (uint8[1][] memory) { + return array1; + } + + function test2() public returns (int8[2][] memory) { + return array2; + } + + function test3() public returns (uint16[][2] memory) { + return array3; + } + + function test4() public returns (uint8[][2][] memory) { + return array4; + } + + function test5() public returns (string[2][] memory) { + return array5; + } +} + +// ==== +// compileViaYul: also +// ---- +// test1() -> 0x20, 0x10, 0x01, 0x02, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0x10 +// test2() -> 0x20, 0x03, 0x01, 0x02, 3, 4, 5, 6 +// test3() -> 0x20, 0x40, 0xc0, 0x03, 1, 2, 3, 10, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13 +// test4() -> 0x20, 0x03, 0x60, 0x0180, 0x0240, 0x40, 0xc0, 3, 1, 2, 3, 2, 4, 5, 0x40, 0x80, 1, 6, 1, 7, 0x40, 0xe0, 4, 8, 9, 10, 11, 5, 12, 13, 14, 15, 0x10 +// test5() -> 0x20, 3, 0x60, 288, 0x01e0, 0x40, 0x80, 4, 52652770314156132753103522558211245866589054961607131434777992190478746910720, 2, 47696036513692484977101116032555085334762927796157333774492759403894270853120, 0x40, 0x80, 4, 48152679884589002438443377966044670264050608660991796074848385302132292583424, 1, 43874346312576839672212443538448152585028080127215369968075725190498334277632, 0x40, 0x80, 4, 52647538817385212172903286807934654968315727694643370704309751478220717293568, 5, 44076556304902723615564186688849689667362290371271333483270849954029028507648 diff --git a/test/libsolidity/semanticTests/array/multi_dimension_inline_array_to_storage_dynamic_array_assignment.sol b/test/libsolidity/semanticTests/array/multi_dimension_inline_array_to_storage_dynamic_array_assignment.sol new file mode 100644 index 000000000..51867c153 --- /dev/null +++ b/test/libsolidity/semanticTests/array/multi_dimension_inline_array_to_storage_dynamic_array_assignment.sol @@ -0,0 +1,29 @@ +pragma abicoder v2; + +contract C { + uint8[] array1 = [1,2,3,4,5,6,7,8,9,10,11,12,13, 14, 15, 16]; + uint8[][] array2 = [[1, 2, 3],[4, 5],[6,7,8,9,10,11,12], [13,14,15,16]]; + uint8[][][] array3 = [[[1, 2, 3],[4, 5],[6]], [[7, 8, 9, 10,11],[12, 13,14,15,16]]]; + + + function test1() public returns (uint8[] memory) { + return array1; + } + + function test2() public returns (uint8[][] memory) { + return array2; + } + + function test3() public returns (uint8[][][] memory) { + return array3; + } +} + +// ==== +// EVMVersion: >=constantinople +// compileToEwasm: also +// compileViaYul: also +// ---- +// test1() -> 0x20, 0x10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0x10 +// test2() -> 0x20, 4, 0x80, 0x0100, 0x0160, 0x0260, 3, 1, 2, 3, 2, 4, 5, 7, 6, 7, 8, 9, 10, 11, 12, 4, 13, 14, 15, 0x10 +// test3() -> 0x20, 0x02, 0x40, 0x01e0, 0x03, 0x60, 0xe0, 0x0140, 3, 1, 2, 3, 2, 4, 5, 1, 6, 2, 0x40, 0x0100, 5, 7, 8, 9, 10, 11, 5, 12, 13, 14, 15, 0x10 diff --git a/test/libsolidity/semanticTests/array/multi_dimension_inline_array_to_storage_static_array_assignment.sol b/test/libsolidity/semanticTests/array/multi_dimension_inline_array_to_storage_static_array_assignment.sol new file mode 100644 index 000000000..45b877117 --- /dev/null +++ b/test/libsolidity/semanticTests/array/multi_dimension_inline_array_to_storage_static_array_assignment.sol @@ -0,0 +1,25 @@ +contract C { + uint16[1][16] array1 = [[1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [11], [12], [13], [14], [15], [16]]; + uint16[4][2] array2 = [[1, 2, 3, 4], [5, 6, 7, 8]]; + uint8[4][2][2] array3 = [[[1, 2, 3, 4], [5, 6, 7, 8]], [[9, 10, 11, 12], [13, 14, 15, 16]]]; + + function test1() public returns (uint16[1][16] memory) { + return array1; + } + + function test2() public returns (uint16[4][2] memory) { + return array2; + } + + function test3() public returns (uint8[4][2][2] memory) { + return array3; + } +} + +// ==== +// compileToEwasm: also +// compileViaYul: also +// ---- +// test1() -> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 +// test2() -> 1, 2, 3, 4, 5, 6, 7, 8 +// test3() -> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 diff --git a/test/libsolidity/semanticTests/array/push/array_push_nested_from_calldata.sol b/test/libsolidity/semanticTests/array/push/array_push_nested_from_calldata.sol index 63f974498..4af923af8 100644 --- a/test/libsolidity/semanticTests/array/push/array_push_nested_from_calldata.sol +++ b/test/libsolidity/semanticTests/array/push/array_push_nested_from_calldata.sol @@ -13,5 +13,5 @@ contract C { // ---- // f(uint120[]): 0x20, 3, 1, 2, 3 -> 1 // gas irOptimized: 112832 -// gas legacy: 113686 -// gas legacyOptimized: 113499 +// gas legacy: 113687 +// gas legacyOptimized: 113500 diff --git a/test/libsolidity/semanticTests/array/push/array_push_struct.sol b/test/libsolidity/semanticTests/array/push/array_push_struct.sol index 04f94b3db..f761960e1 100644 --- a/test/libsolidity/semanticTests/array/push/array_push_struct.sol +++ b/test/libsolidity/semanticTests/array/push/array_push_struct.sol @@ -21,5 +21,5 @@ contract c { // ---- // test() -> 2, 3, 4, 5 // gas irOptimized: 135204 -// gas legacy: 147484 -// gas legacyOptimized: 146456 +// gas legacy: 147486 +// gas legacyOptimized: 146458 diff --git a/test/libsolidity/semanticTests/various/skip_dynamic_types_for_structs.sol b/test/libsolidity/semanticTests/various/skip_dynamic_types_for_structs.sol index cd34a419d..92cdf9308 100644 --- a/test/libsolidity/semanticTests/various/skip_dynamic_types_for_structs.sol +++ b/test/libsolidity/semanticTests/various/skip_dynamic_types_for_structs.sol @@ -20,6 +20,6 @@ contract C { // ---- // g() -> 2, 6 -// gas irOptimized: 178549 +// gas irOptimized: 178177 // gas legacy: 180893 -// gas legacyOptimized: 179394 +// gas legacyOptimized: 179388 diff --git a/test/libsolidity/smtCheckerTests/operators/delete_tuple.sol b/test/libsolidity/smtCheckerTests/operators/delete_tuple.sol index ab8a1bb20..0d8ae08ae 100644 --- a/test/libsolidity/smtCheckerTests/operators/delete_tuple.sol +++ b/test/libsolidity/smtCheckerTests/operators/delete_tuple.sol @@ -6,3 +6,4 @@ contract A{ // ==== // SMTEngine: all // ---- +// TypeError 4247: (50-57): Expression has to be an lvalue. diff --git a/test/libsolidity/syntaxTests/array/concat/bytes_concat_wrong_type_empty_array_literal.sol b/test/libsolidity/syntaxTests/array/concat/bytes_concat_wrong_type_empty_array_literal.sol index 86ee748ed..41350e691 100644 --- a/test/libsolidity/syntaxTests/array/concat/bytes_concat_wrong_type_empty_array_literal.sol +++ b/test/libsolidity/syntaxTests/array/concat/bytes_concat_wrong_type_empty_array_literal.sol @@ -4,4 +4,6 @@ contract C { } } // ---- -// TypeError 6378: (60-62): Unable to deduce common type for array elements. +// TypeError 8015: (60-62): Invalid type for argument in the bytes.concat function call. bytes or fixed bytes type is required, but inline_array() provided. +// TypeError 8015: (64-66): Invalid type for argument in the bytes.concat function call. bytes or fixed bytes type is required, but inline_array() provided. +// TypeError 8015: (68-70): Invalid type for argument in the bytes.concat function call. bytes or fixed bytes type is required, but inline_array() provided. diff --git a/test/libsolidity/syntaxTests/array/concat/bytes_concat_wrong_type_misc_literals_and_expressions.sol b/test/libsolidity/syntaxTests/array/concat/bytes_concat_wrong_type_misc_literals_and_expressions.sol index 8d5374e84..dafd877df 100644 --- a/test/libsolidity/syntaxTests/array/concat/bytes_concat_wrong_type_misc_literals_and_expressions.sol +++ b/test/libsolidity/syntaxTests/array/concat/bytes_concat_wrong_type_misc_literals_and_expressions.sol @@ -50,7 +50,7 @@ contract C { // TypeError 8015: (796-797): Invalid type for argument in the bytes.concat function call. bytes or fixed bytes type is required, but function () provided. // TypeError 8015: (811-813): Invalid type for argument in the bytes.concat function call. bytes or fixed bytes type is required, but tuple() provided. // TypeError 8015: (827-833): Invalid type for argument in the bytes.concat function call. bytes or fixed bytes type is required, but tuple(int_const 0,int_const 0) provided. -// TypeError 8015: (847-850): Invalid type for argument in the bytes.concat function call. bytes or fixed bytes type is required, but uint8[1] memory provided. +// TypeError 8015: (847-850): Invalid type for argument in the bytes.concat function call. bytes or fixed bytes type is required, but inline_array(int_const 0) provided. // TypeError 8015: (864-870): Invalid type for argument in the bytes.concat function call. bytes or fixed bytes type is required, but uint8[1] memory slice provided. // TypeError 8015: (884-890): Invalid type for argument in the bytes.concat function call. bytes or fixed bytes type is required, but uint8 provided. // TypeError 8015: (904-911): Invalid type for argument in the bytes.concat function call. bytes or fixed bytes type is required, but contract C provided. diff --git a/test/libsolidity/syntaxTests/conversion/function_type_array_to_memory.sol b/test/libsolidity/syntaxTests/conversion/function_type_array_to_memory.sol index 179eafdc9..ee686db48 100644 --- a/test/libsolidity/syntaxTests/conversion/function_type_array_to_memory.sol +++ b/test/libsolidity/syntaxTests/conversion/function_type_array_to_memory.sol @@ -11,7 +11,6 @@ contract C { function () external returns(uint)[1] memory externalDefaultArray; function () internal returns(uint)[1] memory internalDefaultArray; - // This would work if we were assigning to storage rather than memory externalDefaultArray = [this.externalView]; internalDefaultArray = [internalView]; @@ -22,7 +21,6 @@ contract C { function () external returns(uint)[1] memory externalDefaultArray; function () internal returns(uint)[1] memory internalDefaultArray; - // This would work if we were assigning to storage rather than memory externalDefaultArray = [this.externalPure]; internalDefaultArray = [internalPure]; @@ -33,7 +31,6 @@ contract C { function () external returns(uint)[1] memory externalViewArray; function () internal returns(uint)[1] memory internalViewArray; - // This would work if we were assigning to storage rather than memory externalViewArray = [this.externalPure]; internalViewArray = [internalPure]; @@ -41,9 +38,7 @@ contract C { } } // ---- -// TypeError 7407: (760-779): Type function () view external returns (uint256)[1] memory is not implicitly convertible to expected type function () external returns (uint256)[1] memory. -// TypeError 7407: (812-826): Type function () view returns (uint256)[1] memory is not implicitly convertible to expected type function () returns (uint256)[1] memory. -// TypeError 7407: (1230-1249): Type function () pure external returns (uint256)[1] memory is not implicitly convertible to expected type function () external returns (uint256)[1] memory. -// TypeError 7407: (1282-1296): Type function () pure returns (uint256)[1] memory is not implicitly convertible to expected type function () returns (uint256)[1] memory. -// TypeError 7407: (1688-1707): Type function () pure external returns (uint256)[1] memory is not implicitly convertible to expected type function () external returns (uint256)[1] memory. -// TypeError 7407: (1737-1751): Type function () pure returns (uint256)[1] memory is not implicitly convertible to expected type function () returns (uint256)[1] memory. +// Warning 2018: (17-81): Function state mutability can be restricted to pure +// Warning 2018: (86-152): Function state mutability can be restricted to pure +// Warning 2018: (229-293): Function state mutability can be restricted to pure +// Warning 2018: (298-364): Function state mutability can be restricted to pure diff --git a/test/libsolidity/syntaxTests/conversion/implicit_conversion_from_array_of_string_literals_to_calldata_string.sol b/test/libsolidity/syntaxTests/conversion/implicit_conversion_from_array_of_string_literals_to_calldata_string.sol index cd74b985c..8fdc0e2c1 100644 --- a/test/libsolidity/syntaxTests/conversion/implicit_conversion_from_array_of_string_literals_to_calldata_string.sol +++ b/test/libsolidity/syntaxTests/conversion/implicit_conversion_from_array_of_string_literals_to_calldata_string.sol @@ -1,4 +1,4 @@ -pragma abicoder v2; +pragma abicoder v2; contract C { function f() public pure returns(string[5] calldata) { @@ -6,4 +6,4 @@ contract C { } } // ---- -// TypeError 6359: (122-147): Return argument type string[5] memory is not implicitly convertible to expected type (type of first return variable) string[5] calldata. +// TypeError 6359: (108-133): Return argument type inline_array(literal_string "h", literal_string "e", literal_string "l", literal_string "l", literal_string "o") is not implicitly convertible to expected type (type of first return variable) string[5] calldata. Invalid conversion from literal_string "h" to string calldata. diff --git a/test/libsolidity/syntaxTests/inline_arrays/access_index_omitted.sol b/test/libsolidity/syntaxTests/inline_arrays/access_index_omitted.sol new file mode 100644 index 000000000..cb9018011 --- /dev/null +++ b/test/libsolidity/syntaxTests/inline_arrays/access_index_omitted.sol @@ -0,0 +1,7 @@ +contract C { + function f() public { + uint i = [0, 1, 2][]; + } +} +// ---- +// TypeError 5093: (56-67): Index expression cannot be omitted. diff --git a/test/libsolidity/syntaxTests/inline_arrays/inline_array_implicit_conversion_to_array_of_bytes.sol b/test/libsolidity/syntaxTests/inline_arrays/inline_array_implicit_conversion_to_array_of_bytes.sol new file mode 100644 index 000000000..93774673e --- /dev/null +++ b/test/libsolidity/syntaxTests/inline_arrays/inline_array_implicit_conversion_to_array_of_bytes.sol @@ -0,0 +1,8 @@ +contract C { + function f() external pure { + bytes[2] memory a1 = ['foo', 'bar']; + bytes[2] memory a2 = [hex'666f6f', hex'626172']; + require(keccak256(a1[0]) == keccak256(a2[0])); + require(keccak256(a1[1]) == keccak256(a2[1])); + } +} diff --git a/test/libsolidity/syntaxTests/inline_arrays/inline_array_passed_as_function_parameter.sol b/test/libsolidity/syntaxTests/inline_arrays/inline_array_passed_as_function_parameter.sol new file mode 100644 index 000000000..d9710dc8c --- /dev/null +++ b/test/libsolidity/syntaxTests/inline_arrays/inline_array_passed_as_function_parameter.sol @@ -0,0 +1,13 @@ +contract C { + function f(uint256[] memory x) private {} + function f(uint256[3] memory x) private {} + + function g() private { + f([1]); + f([1,2,3]); + f([1,2,3,4]); + } +} + +// ---- +// TypeError 4487: (158-159): No unique declaration found after argument-dependent lookup. diff --git a/test/libsolidity/syntaxTests/inline_arrays/inline_array_with_invalid_utf8_sequence_explicit_conversion.sol b/test/libsolidity/syntaxTests/inline_arrays/inline_array_with_invalid_utf8_sequence_explicit_conversion.sol new file mode 100644 index 000000000..6a048190c --- /dev/null +++ b/test/libsolidity/syntaxTests/inline_arrays/inline_array_with_invalid_utf8_sequence_explicit_conversion.sol @@ -0,0 +1,9 @@ +contract C { + function f() external pure { + string[2] memory a1 = [string(bytes(hex'74000001')), string(bytes(hex'c0a80101'))]; + bytes[2] memory a2 = [bytes(hex'74000001'), bytes(hex'c0a80101')]; + } +} +// ---- +// Warning 2072: (54-73): Unused local variable. +// Warning 2072: (146-164): Unused local variable. diff --git a/test/libsolidity/syntaxTests/inline_arrays/inline_array_with_invalid_utf8_sequence_implicit_conversion.sol b/test/libsolidity/syntaxTests/inline_arrays/inline_array_with_invalid_utf8_sequence_implicit_conversion.sol new file mode 100644 index 000000000..e3e0b43bc --- /dev/null +++ b/test/libsolidity/syntaxTests/inline_arrays/inline_array_with_invalid_utf8_sequence_implicit_conversion.sol @@ -0,0 +1,11 @@ +contract C { + function f() external pure { + string[2] memory a1 = [hex'74000001', hex'c0a80101']; + string[2] memory a2 = [bytes(hex'74000001'), bytes(hex'c0a80101')]; + bytes[2] memory a3 = [hex'74000001', hex'c0a80101']; + bytes[2] memory a4 = ['foo', 'bar']; + } +} +// ---- +// TypeError 9574: (54-106): Type inline_array(literal_string hex"74000001", literal_string hex"c0a80101") is not implicitly convertible to expected type string[2] memory. Invalid conversion from literal_string hex"c0a80101" to string memory. Contains invalid UTF-8 sequence at position 4. +// TypeError 9574: (116-182): Type inline_array(bytes memory, bytes memory) is not implicitly convertible to expected type string[2] memory. Invalid conversion from bytes memory to string memory. diff --git a/test/libsolidity/syntaxTests/inline_arrays/invalid_size_of_inline_array.sol b/test/libsolidity/syntaxTests/inline_arrays/invalid_size_of_inline_array.sol new file mode 100644 index 000000000..396db0de4 --- /dev/null +++ b/test/libsolidity/syntaxTests/inline_arrays/invalid_size_of_inline_array.sol @@ -0,0 +1,7 @@ +contract C { + function f() public { + uint[3] memory x = [1, 2]; + } +} +// ---- +// TypeError 9574: (47-72): Type inline_array(int_const 1, int_const 2) is not implicitly convertible to expected type uint256[3] memory. Number of components in array literal (2) does not match array size (3). diff --git a/test/libsolidity/syntaxTests/inline_arrays/invalid_types_in_inline_array.sol b/test/libsolidity/syntaxTests/inline_arrays/invalid_types_in_inline_array.sol index 1ba1fdb1d..64cab00c8 100644 --- a/test/libsolidity/syntaxTests/inline_arrays/invalid_types_in_inline_array.sol +++ b/test/libsolidity/syntaxTests/inline_arrays/invalid_types_in_inline_array.sol @@ -4,4 +4,4 @@ contract C { } } // ---- -// TypeError 6378: (66-83): Unable to deduce common type for array elements. +// TypeError 9574: (47-83): Type inline_array(int_const 45, literal_string "foo", bool) is not implicitly convertible to expected type uint256[3] memory. Invalid conversion from literal_string "foo" to uint256. diff --git a/test/libsolidity/syntaxTests/specialFunctions/encodeCall_fail_args.sol b/test/libsolidity/syntaxTests/specialFunctions/encodeCall_fail_args.sol index 48372263f..b017dac86 100644 --- a/test/libsolidity/syntaxTests/specialFunctions/encodeCall_fail_args.sol +++ b/test/libsolidity/syntaxTests/specialFunctions/encodeCall_fail_args.sol @@ -24,4 +24,4 @@ contract C { // TypeError 7788: (382-408): Expected 1 instead of 0 components for the tuple parameter. // TypeError 6219: (489-511): Expected two arguments: a function pointer followed by a tuple. // TypeError 7515: (597-628): Expected a tuple with 2 components instead of a single non-tuple parameter. -// TypeError 5407: (621-627): Cannot implicitly convert component at position 0 from "uint8[2] memory" to "int256". +// TypeError 5407: (621-627): Cannot implicitly convert component at position 0 from "inline_array(int_const 1, int_const 2)" to "int256": Array literal can not be converted to byte array or string. diff --git a/test/libsolidity/syntaxTests/string/concat/string_concat_wrong_type_empty_array_literal.sol b/test/libsolidity/syntaxTests/string/concat/string_concat_wrong_type_empty_array_literal.sol index 8c204ffca..e43ec9828 100644 --- a/test/libsolidity/syntaxTests/string/concat/string_concat_wrong_type_empty_array_literal.sol +++ b/test/libsolidity/syntaxTests/string/concat/string_concat_wrong_type_empty_array_literal.sol @@ -4,4 +4,6 @@ contract C { } } // ---- -// TypeError 6378: (61-63): Unable to deduce common type for array elements. +// TypeError 9977: (61-63): Invalid type for argument in the string.concat function call. string type is required, but t_inline_array$__$ provided. +// TypeError 9977: (65-67): Invalid type for argument in the string.concat function call. string type is required, but t_inline_array$__$ provided. +// TypeError 9977: (69-71): Invalid type for argument in the string.concat function call. string type is required, but t_inline_array$__$ provided. diff --git a/test/libsolidity/syntaxTests/string/concat/string_concat_wrong_type_misc_literals_and_expressions.sol b/test/libsolidity/syntaxTests/string/concat/string_concat_wrong_type_misc_literals_and_expressions.sol index f8e62b49e..7333e0fe3 100644 --- a/test/libsolidity/syntaxTests/string/concat/string_concat_wrong_type_misc_literals_and_expressions.sol +++ b/test/libsolidity/syntaxTests/string/concat/string_concat_wrong_type_misc_literals_and_expressions.sol @@ -50,7 +50,7 @@ contract C { // TypeError 9977: (797-798): Invalid type for argument in the string.concat function call. string type is required, but t_function_internal_nonpayable$__$returns$__$ provided. // TypeError 9977: (812-814): Invalid type for argument in the string.concat function call. string type is required, but t_tuple$__$ provided. // TypeError 9977: (828-834): Invalid type for argument in the string.concat function call. string type is required, but t_tuple$_t_rational_0_by_1_$_t_rational_0_by_1_$ provided. -// TypeError 9977: (848-851): Invalid type for argument in the string.concat function call. string type is required, but t_array$_t_uint8_$1_memory_ptr provided. +// TypeError 9977: (848-851): Invalid type for argument in the string.concat function call. string type is required, but t_inline_array$_t_rational_0_by_1_$ provided. // TypeError 9977: (865-871): Invalid type for argument in the string.concat function call. string type is required, but t_array$_t_uint8_$1_memory_ptr_slice provided. // TypeError 9977: (885-891): Invalid type for argument in the string.concat function call. string type is required, but t_uint8 provided. // TypeError 9977: (905-912): Invalid type for argument in the string.concat function call. string type is required, but t_contract$_C_$61 provided.