mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Add InlineArrayType to support literals conversion to statically and dynamically allocated arrays.
This commit is contained in:
parent
7bfec3ba70
commit
1ef2f60049
@ -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:
|
||||
|
@ -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);
|
||||
|
@ -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<InlineArrayType const&>(_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<RationalNumberType const&>(*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<ASTPointer<Expression const>> 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<InlineArrayType const&>(*baseType).componentsCommonMobileType();
|
||||
break;
|
||||
}
|
||||
case Type::Category::Mapping:
|
||||
{
|
||||
MappingType const& actualType = dynamic_cast<MappingType const&>(*baseType);
|
||||
@ -3469,6 +3474,8 @@ bool TypeChecker::visit(IndexRangeAccess const& _access)
|
||||
ArrayType const* arrayType = nullptr;
|
||||
if (auto const* arraySlice = dynamic_cast<ArraySliceType const*>(exprType))
|
||||
arrayType = &arraySlice->arrayType();
|
||||
else if (auto const* inlineArray = dynamic_cast<InlineArrayType const*>(exprType))
|
||||
arrayType = TypeProvider::array(DataLocation::Memory, inlineArray->componentsCommonMobileType(), inlineArray->components().size());
|
||||
else if (!(arrayType = dynamic_cast<ArrayType const*>(exprType)))
|
||||
m_errorReporter.fatalTypeError(4781_error, _access.location(), "Index range access is only possible for arrays and array slices.");
|
||||
|
||||
|
@ -407,6 +407,11 @@ TupleType const* TypeProvider::tuple(vector<Type const*> members)
|
||||
return createAndGet<TupleType>(move(members));
|
||||
}
|
||||
|
||||
InlineArrayType const* TypeProvider::inlineArray(vector<Type const*> _members)
|
||||
{
|
||||
return createAndGet<InlineArrayType>(move(_members));
|
||||
}
|
||||
|
||||
ReferenceType const* TypeProvider::withLocation(ReferenceType const* _type, DataLocation _location, bool _isPointer)
|
||||
{
|
||||
if (_type->location() == _location && _type->isPointer() == _isPointer)
|
||||
|
@ -109,6 +109,8 @@ public:
|
||||
|
||||
static TupleType const* emptyTuple() noexcept { return &m_emptyTuple; }
|
||||
|
||||
static InlineArrayType const* inlineArray(std::vector<Type const*> 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)
|
||||
|
@ -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<ArrayType const*>(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<ArrayType const*>(&_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<unsigned>()) + ")."
|
||||
);
|
||||
|
||||
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<InlineArrayType const*>(&_other))
|
||||
{
|
||||
std::vector<Type const*> 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<string> 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<tuple<string, Type const*>> InlineArrayType::makeStackItems() const
|
||||
{
|
||||
vector<tuple<string, Type const*>> 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()),
|
||||
|
@ -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<Type const*> const m_components;
|
||||
};
|
||||
|
||||
class InlineArrayType: public CompositeType
|
||||
{
|
||||
public:
|
||||
explicit InlineArrayType(std::vector<Type const*> _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<Type const*> const& components() const { return m_components; }
|
||||
|
||||
protected:
|
||||
std::vector<std::tuple<std::string, Type const*>> makeStackItems() const override;
|
||||
std::vector<Type const*> 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<Type const*> 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
|
||||
|
@ -29,6 +29,7 @@
|
||||
#include <libsolutil/StringUtils.h>
|
||||
|
||||
#include <boost/algorithm/string/join.hpp>
|
||||
#include <range/v3/view/enumerate.hpp>
|
||||
|
||||
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<InlineArrayType const&>(_from),
|
||||
dynamic_cast<ArrayType const&>(_to),
|
||||
_options
|
||||
);
|
||||
|
||||
}
|
||||
else if (auto toArray = dynamic_cast<ArrayType const*>(&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<map<string, string>> memberSetValues;
|
||||
unsigned stackItemIndex = 0;
|
||||
|
||||
for (auto const& type: _from.components())
|
||||
{
|
||||
memberSetValues.emplace_back();
|
||||
|
||||
memberSetValues.back()["setMember"] = Whiskers(R"(
|
||||
<?usesTail>
|
||||
mstore(pos, sub(tail, headStart))
|
||||
tail := <encodeToMemoryFun>(<value>, tail)
|
||||
pos := add(pos, 0x20)
|
||||
<!usesTail>
|
||||
pos := <encodeToMemoryFun>(<value>, pos)
|
||||
</usesTail>
|
||||
)")
|
||||
("value", suffixedVariableNameList("var_", stackItemIndex, stackItemIndex + type->sizeOnStack()))
|
||||
("usesTail", usesTail)
|
||||
("encodeToMemoryFun", abiEncodeAndReturnUpdatedPosFunction(*type, *_to.baseType(), subOptions))
|
||||
.render();
|
||||
|
||||
stackItemIndex += type->sizeOnStack();
|
||||
}
|
||||
|
||||
return Whiskers(R"(
|
||||
// <readableTypeNameFrom> -> <readableTypeNameTo>
|
||||
function <functionName>(<values>, pos) <return> {
|
||||
let length := <length>
|
||||
pos := <storeLength>(pos, length)
|
||||
|
||||
<?usesTail>
|
||||
let headStart := pos
|
||||
let tail := add(pos, mul(length, 0x20))
|
||||
<#member>
|
||||
<setMember>
|
||||
</member>
|
||||
pos := tail
|
||||
<!usesTail>
|
||||
<#member>
|
||||
<setMember>
|
||||
</member>
|
||||
</usesTail>
|
||||
|
||||
<assignEnd>
|
||||
}
|
||||
)")
|
||||
("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,
|
||||
|
@ -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,
|
||||
|
@ -36,6 +36,9 @@
|
||||
#include <libevmasm/Instruction.h>
|
||||
#include <liblangutil/Exceptions.h>
|
||||
|
||||
#include <range/v3/view/reverse.hpp>
|
||||
#include <range/v3/view/enumerate.hpp>
|
||||
|
||||
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<ArrayType const*>(_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<StringLiteralType const*>(sourceComponentType))
|
||||
copyLiteralToStorage(*stringLiteralType);
|
||||
else
|
||||
{
|
||||
InlineArrayType const* sourceType = dynamic_cast<InlineArrayType const*>(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<RationalNumberType const*>(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<unsigned>(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<size_t>(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;
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -32,6 +32,7 @@
|
||||
#include <libevmasm/Instruction.h>
|
||||
#include <libsolutil/Whiskers.h>
|
||||
#include <libsolutil/StackTooDeepString.h>
|
||||
#include <range/v3/view/reverse.hpp>
|
||||
|
||||
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<InlineArrayType const*>(_givenTypes[i]))
|
||||
{
|
||||
ArrayType const* arrayType = dynamic_cast<ArrayType const*>(_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: ... <end_of_mem>
|
||||
if (_givenTypes[i]->category() == Type::Category::StringLiteral)
|
||||
if (InlineArrayType const* inlineArrayType = dynamic_cast<InlineArrayType const*>(_givenTypes[i]))
|
||||
{
|
||||
ArrayType const* arrayType = dynamic_cast<ArrayType const*>(_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<StringLiteralType const&>(*_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<InlineArrayType const&>(_typeOnStack);
|
||||
ArrayType const& arrayType = dynamic_cast<ArrayType const&>(_targetType);
|
||||
|
||||
solAssert(arrayType.location() == DataLocation::Memory);
|
||||
|
||||
auto const& components = inlineArray.components();
|
||||
m_context << u256(components.size());
|
||||
// stack: <source ref> <length>
|
||||
ArrayUtils(m_context).convertLengthToSize(arrayType, true);
|
||||
|
||||
// stack: <source ref> <size>
|
||||
if (arrayType.isDynamicallySized())
|
||||
m_context << u256(0x20) << Instruction::ADD;
|
||||
// <size> = <size> + 0x20
|
||||
allocateMemory();
|
||||
|
||||
// stack: <source ref> <mem start>
|
||||
m_context << Instruction::DUP1;
|
||||
// stack: <source ref> <mem start> <mem start>
|
||||
if (arrayType.isDynamicallySized())
|
||||
{
|
||||
m_context << u256(components.size());
|
||||
// stack: <source ref> <mem start> <mem start> <length>
|
||||
storeInMemoryDynamic(*TypeProvider::uint256());
|
||||
// memory[<mem start>] = <length>
|
||||
// stack: <source ref> <mem start> <mem data pos>
|
||||
}
|
||||
|
||||
// stack: <source ref> <mem start> <mem data pos>
|
||||
m_context << u256(components.size() * arrayType.baseType()->memoryHeadSize()) << Instruction::ADD;
|
||||
// stack: <source ref> <mem start> <mem data end>
|
||||
for (Type const* component: components | ranges::views::reverse)
|
||||
{
|
||||
// stack: <source ref> <mem start> <component end>
|
||||
m_context << u256(arrayType.memoryStride()) << Instruction::SWAP1 << Instruction::SUB;
|
||||
// stack: <source ref> <mem start> <component pos>
|
||||
m_context << Instruction::DUP1;
|
||||
// stack: <source ref> <mem start> <component pos> <component pos>
|
||||
|
||||
unsigned const componentSize = component->sizeOnStack();
|
||||
moveToStackTop(3, componentSize);
|
||||
// stack: <source ref> <mem start> <component pos> <component pos> <value>
|
||||
convertType(*component, *arrayType.baseType());
|
||||
// stack: <source ref> <mem start> <component pos> <component pos> <converted value>
|
||||
storeInMemoryDynamic(*arrayType.baseType());
|
||||
// stack: <source ref> <mem start> <component pos> <component end>
|
||||
m_context << Instruction::POP;
|
||||
// stack: <source ref> <mem start> <component pos>
|
||||
}
|
||||
// stack: <mem start> <mem data pos>
|
||||
m_context << Instruction::POP;
|
||||
// stack: <mem start>
|
||||
break;
|
||||
}
|
||||
case Type::Category::ArraySlice:
|
||||
{
|
||||
auto& typeOnStack = dynamic_cast<ArraySliceType const&>(_typeOnStack);
|
||||
@ -1287,6 +1366,7 @@ void CompilerUtils::convertType(
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case Type::Category::Bool:
|
||||
solAssert(_targetType == _typeOnStack, "Invalid conversion for bool.");
|
||||
if (_cleanupNeeded)
|
||||
|
@ -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<InlineArrayType const*>(_type))
|
||||
{
|
||||
auto targetArray = dynamic_cast<ArrayType const*>(_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<ArrayType const&>(*_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<unique_ptr<LValue>> 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<unique_ptr<LValue>> 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<LValue>());
|
||||
if (_tuple.annotation().willBeWrittenTo)
|
||||
{
|
||||
if (_tuple.components().size() == 1)
|
||||
m_currentLValue = move(lvalues[0]);
|
||||
else
|
||||
m_currentLValue = make_unique<TupleObject>(m_context, move(lvalues));
|
||||
}
|
||||
else if (_tuple.annotation().willBeWrittenTo)
|
||||
lvalues.push_back(unique_ptr<LValue>());
|
||||
if (_tuple.annotation().willBeWrittenTo)
|
||||
{
|
||||
if (_tuple.components().size() == 1)
|
||||
m_currentLValue = move(lvalues[0]);
|
||||
else
|
||||
m_currentLValue = make_unique<TupleObject>(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<InlineArrayType const&>(baseType);
|
||||
solAssert(_indexAccess.indexExpression(), "Index expression expected.");
|
||||
|
||||
ArrayType const* arrayType = TypeProvider::array(DataLocation::Memory, inlineArrayType.componentsCommonMobileType(), inlineArrayType.components().size());
|
||||
|
||||
// stack layout: <source ref> (variably sized)
|
||||
acceptAndConvert(_indexAccess.baseExpression(), *arrayType, true);
|
||||
utils().moveIntoStack(inlineArrayType.sizeOnStack());
|
||||
utils().popStackSlots(inlineArrayType.sizeOnStack());
|
||||
// stack layout: <array_ref> [<length>]
|
||||
|
||||
acceptAndConvert(*_indexAccess.indexExpression(), *TypeProvider::uint256(), true);
|
||||
// stack layout: <array_ref> [<length>] <index>
|
||||
|
||||
ArrayUtils(m_context).accessIndex(*arrayType, true);
|
||||
setLValue<MemoryItem>(_indexAccess, *arrayType->baseType());
|
||||
|
||||
break;
|
||||
}
|
||||
case Type::Category::FixedBytes:
|
||||
{
|
||||
FixedBytesType const& fixedBytesType = dynamic_cast<FixedBytesType const&>(baseType);
|
||||
|
@ -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<ArrayType const&>(*m_dataType),
|
||||
dynamic_cast<ArrayType const&>(_sourceType)
|
||||
dynamic_cast<InlineArrayType const&>(_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<StructType const&>(*m_dataType);
|
||||
auto const& sourceType = dynamic_cast<StructType const&>(_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<u256, unsigned> 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<u256, unsigned> 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<ArrayType const&>(*m_dataType),
|
||||
dynamic_cast<ArrayType const&>(_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<StructType const&>(*m_dataType);
|
||||
auto const& sourceType = dynamic_cast<StructType const&>(_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<u256, unsigned> 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<u256, unsigned> 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."));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -31,6 +31,8 @@
|
||||
#include <libsolutil/StringUtils.h>
|
||||
#include <libsolidity/ast/TypeProvider.h>
|
||||
|
||||
#include <range/v3/view/enumerate.hpp>
|
||||
|
||||
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<map<string, string>> memberSetValues;
|
||||
unsigned stackItemIndex = 0;
|
||||
for (Type const* type: _fromType.components())
|
||||
{
|
||||
memberSetValues.emplace_back();
|
||||
memberSetValues.back()["setMember"] = Whiskers(R"({
|
||||
<updateStorageValue>(elementSlot, elementOffset<value>)
|
||||
|
||||
<?multipleItemsPerSlot>
|
||||
elementOffset := add(elementOffset, <storageStride>)
|
||||
if gt(elementOffset, sub(32, <storageStride>)) {
|
||||
elementOffset := 0
|
||||
elementSlot := add(elementSlot, 1)
|
||||
}
|
||||
<!multipleItemsPerSlot>
|
||||
elementSlot := add(elementSlot, <storageSize>)
|
||||
</multipleItemsPerSlot>
|
||||
})")
|
||||
("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 <functionName>(slot<values>) {
|
||||
let length := <arrayLength>
|
||||
<resizeArray>(slot, length)
|
||||
|
||||
let elementSlot := <dstDataLocation>(slot)
|
||||
let elementOffset := 0
|
||||
|
||||
<#member>
|
||||
<setMember>
|
||||
</member>
|
||||
|
||||
<?multipleItemsPerSlot>
|
||||
if gt(elementOffset, 0) {
|
||||
<partialClearStorageSlotFunction>(elementSlot, elementOffset)
|
||||
}
|
||||
</multipleItemsPerSlot>
|
||||
}
|
||||
)");
|
||||
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<ReferenceType const*>(&_fromType);
|
||||
solAssert(toReferenceType, "");
|
||||
|
||||
Whiskers templ(R"(
|
||||
function <functionName>(slot<?dynamicOffset>,offset </dynamicOffset><extraParams>) {
|
||||
<?dynamicOffset>if offset { <panic>() }</dynamicOffset>
|
||||
<copyToStorage>(slot<extraParams>)
|
||||
}
|
||||
)");
|
||||
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<ArrayType const&>(*toReferenceType).isByteArrayOrString(), "");
|
||||
|
||||
templ("extraParams", _fromType.sizeOnStack() ?
|
||||
", " + suffixedVariableNameList("value_", 0, _fromType.sizeOnStack()) : "");
|
||||
templ("copyToStorage", copyInlineArrayToStorageFunction(
|
||||
dynamic_cast<InlineArrayType const&>(_fromType),
|
||||
dynamic_cast<ArrayType const&>(_toType)
|
||||
));
|
||||
|
||||
return templ.render();
|
||||
}
|
||||
|
||||
if (!fromReferenceType)
|
||||
{
|
||||
solAssert(_fromType.category() == Type::Category::StringLiteral, "");
|
||||
@ -2827,17 +2926,10 @@ string YulUtilFunctions::updateStorageValueFunction(
|
||||
auto const& toArrayType = dynamic_cast<ArrayType const&>(*toReferenceType);
|
||||
solAssert(toArrayType.isByteArrayOrString(), "");
|
||||
|
||||
return Whiskers(R"(
|
||||
function <functionName>(slot<?dynamicOffset>, offset</dynamicOffset>) {
|
||||
<?dynamicOffset>if offset { <panic>() }</dynamicOffset>
|
||||
<copyToStorage>(slot)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("dynamicOffset", !_offset.has_value())
|
||||
("panic", panicFunction(PanicCode::Generic))
|
||||
("copyToStorage", copyLiteralToStorageFunction(dynamic_cast<StringLiteralType const&>(_fromType).value()))
|
||||
.render();
|
||||
templ("extraParams", "");
|
||||
templ("copyToStorage", copyLiteralToStorageFunction(dynamic_cast<StringLiteralType const&>(_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 <functionName>(slot, <?dynamicOffset>offset, </dynamicOffset><value>) {
|
||||
<?dynamicOffset>if offset { <panic>() }</dynamicOffset>
|
||||
<copyToStorage>(slot, <value>)
|
||||
}
|
||||
)");
|
||||
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<ArrayType const&>(_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<ArrayType const&>(_to));
|
||||
}
|
||||
else if (_from.category() == Type::Category::InlineArray)
|
||||
{
|
||||
solAssert(_to.category() == Type::Category::Array, "");
|
||||
return inlineArrayConversionFunction(dynamic_cast<InlineArrayType const&>(_from), dynamic_cast<ArrayType const&>(_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<map<string, string>> memberSetValues;
|
||||
unsigned stackItemIndex = 0;
|
||||
|
||||
for (auto&& [index, type]: _from.components() | ranges::views::enumerate)
|
||||
{
|
||||
memberSetValues.emplace_back();
|
||||
memberSetValues.back()["setMember"] = Whiskers(R"(
|
||||
let <memberValues> := <conversionFunction>(<value>)
|
||||
<writeToMemory>(add(mpos, <offset>), <memberValues>)
|
||||
)")
|
||||
("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 <functionName>(<values>) -> converted {
|
||||
converted := <allocateArray>(<length>)
|
||||
let mpos := converted
|
||||
<?toDynamic>mpos := add(mpos, 0x20)</toDynamic>
|
||||
<#member>
|
||||
{
|
||||
<setMember>
|
||||
}
|
||||
</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<UserDefinedValueType const*>(&_type))
|
||||
|
@ -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);
|
||||
|
@ -47,6 +47,7 @@
|
||||
#include <libsolutil/FunctionSelector.h>
|
||||
#include <libsolutil/Visitor.h>
|
||||
|
||||
#include <range/v3/view/enumerate.hpp>
|
||||
#include <range/v3/view/transform.hpp>
|
||||
|
||||
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<ArrayType const&>(*_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<optional<IRLValue>> 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<optional<IRLValue>> 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<InlineArrayType const&>(baseType);
|
||||
|
||||
ArrayType const* arrayType = dynamic_cast<ArrayType const*>(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<FixedBytesType const&>(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"(
|
||||
<?checkExtcodesize>
|
||||
if iszero(extcodesize(<address>)) { <revertNoCode>() }
|
||||
@ -3024,6 +3022,12 @@ void IRGeneratorForStatements::writeToLValue(IRLValue const& _lvalue, IRVariable
|
||||
m_utils.copyLiteralToMemoryFunction(literalType->value()) + "()" <<
|
||||
")\n";
|
||||
}
|
||||
else if (dynamic_cast<InlineArrayType const*>(&_value.type()))
|
||||
{
|
||||
solAssert(dynamic_cast<ArrayType const*>(&_lvalue.type));
|
||||
IRVariable value = convert(_value, _lvalue.type);
|
||||
writeToLValue(_lvalue, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
solAssert(_lvalue.type.sizeOnStack() == 1);
|
||||
|
@ -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));
|
||||
|
@ -78,6 +78,10 @@ SortPointer smtSort(frontend::Type const& _type)
|
||||
solAssert(stringLitType, "");
|
||||
array = make_shared<ArraySort>(SortProvider::uintSort, SortProvider::uintSort);
|
||||
}
|
||||
else if (auto const* inlineArrayType = dynamic_cast<InlineArrayType const*>(&_type))
|
||||
{
|
||||
array = make_shared<ArraySort>(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<ArraySliceType const*>(&_type);
|
||||
ArrayType const* arrayType = sliceArrayType ? &sliceArrayType->arrayType() : dynamic_cast<ArrayType const*>(&_type);
|
||||
|
||||
ArrayType const* arrayType = nullptr;
|
||||
if (auto const* inlineArrayType = dynamic_cast<InlineArrayType const*>(&_type))
|
||||
arrayType = TypeProvider::array(
|
||||
DataLocation::Memory,
|
||||
inlineArrayType->componentsCommonMobileType(),
|
||||
inlineArrayType->components().size()
|
||||
);
|
||||
else if (auto const* sliceArrayType = dynamic_cast<ArraySliceType const*>(&_type))
|
||||
arrayType = &sliceArrayType->arrayType();
|
||||
else
|
||||
arrayType = dynamic_cast<ArrayType const*>(&_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<InlineArrayType const*>(_type))
|
||||
{
|
||||
zeroArray = smtutil::Expression::const_array(smtutil::Expression(sortSort), zeroValue(inlineArrayType->componentsCommonMobileType()));
|
||||
length = bigint(inlineArrayType->components().size());
|
||||
}
|
||||
else if (auto mappingType = dynamic_cast<MappingType const*>(_type))
|
||||
zeroArray = smtutil::Expression::const_array(smtutil::Expression(sortSort), zeroValue(mappingType->valueType()));
|
||||
else
|
||||
|
@ -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=()
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -47,5 +47,5 @@ contract C {
|
||||
// ----
|
||||
// f() -> true
|
||||
// gas irOptimized: 146936
|
||||
// gas legacy: 155961
|
||||
// gas legacyOptimized: 153588
|
||||
// gas legacy: 155962
|
||||
// gas legacyOptimized: 153589
|
||||
|
@ -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
|
||||
|
@ -24,5 +24,5 @@ contract c {
|
||||
// ----
|
||||
// test() -> 3, 4
|
||||
// gas irOptimized: 189690
|
||||
// gas legacy: 195353
|
||||
// gas legacyOptimized: 192441
|
||||
// gas legacy: 195355
|
||||
// gas legacyOptimized: 192443
|
||||
|
@ -21,4 +21,4 @@ contract c {
|
||||
// test() -> 0xffffffff, 0x0000000000000000000000000a00090008000700060005000400030002000100, 0x0000000000000000000000000000000000000000000000000000000000000000
|
||||
// gas irOptimized: 124910
|
||||
// gas legacy: 187414
|
||||
// gas legacyOptimized: 165659
|
||||
// gas legacyOptimized: 165660
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -17,5 +17,5 @@ contract C {
|
||||
// ----
|
||||
// test() -> 7
|
||||
// gas irOptimized: 122483
|
||||
// gas legacy: 205196
|
||||
// gas legacyOptimized: 204987
|
||||
// gas legacy: 205197
|
||||
// gas legacyOptimized: 204988
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
||||
|
@ -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
|
@ -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"
|
@ -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
|
@ -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"
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -20,6 +20,6 @@ contract C {
|
||||
|
||||
// ----
|
||||
// g() -> 2, 6
|
||||
// gas irOptimized: 178549
|
||||
// gas irOptimized: 178177
|
||||
// gas legacy: 180893
|
||||
// gas legacyOptimized: 179394
|
||||
// gas legacyOptimized: 179388
|
||||
|
@ -6,3 +6,4 @@ contract A{
|
||||
// ====
|
||||
// SMTEngine: all
|
||||
// ----
|
||||
// TypeError 4247: (50-57): Expression has to be an lvalue.
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -0,0 +1,7 @@
|
||||
contract C {
|
||||
function f() public {
|
||||
uint i = [0, 1, 2][];
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// TypeError 5093: (56-67): Index expression cannot be omitted.
|
@ -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]));
|
||||
}
|
||||
}
|
@ -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.
|
@ -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.
|
@ -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.
|
@ -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).
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user