mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #7178 from ethereum/calldataSizeTest
Split calldataEncodedSize.
This commit is contained in:
commit
16efcfdbb3
@ -22,6 +22,7 @@ Compiler Features:
|
||||
|
||||
Bugfixes:
|
||||
* ABI decoder: Ensure that decoded arrays always point to distinct memory locations.
|
||||
* Code Generator: Treat dynamically encoded but statically sized arrays and structs in calldata properly.
|
||||
* SMTChecker: Fix internal error when inlining functions that contain tuple expressions.
|
||||
* SMTChecker: Fix pointer knowledge erasing in loops.
|
||||
* SMTChecker: Fix internal error when using compound bitwise assignment operators inside branches.
|
||||
|
@ -1647,15 +1647,13 @@ bool ArrayType::validForCalldata() const
|
||||
if (auto arrayBaseType = dynamic_cast<ArrayType const*>(baseType()))
|
||||
if (!arrayBaseType->validForCalldata())
|
||||
return false;
|
||||
return unlimitedCalldataEncodedSize(true) <= numeric_limits<unsigned>::max();
|
||||
return isDynamicallySized() || unlimitedStaticCalldataSize(true) <= numeric_limits<unsigned>::max();
|
||||
}
|
||||
|
||||
bigint ArrayType::unlimitedCalldataEncodedSize(bool _padded) const
|
||||
bigint ArrayType::unlimitedStaticCalldataSize(bool _padded) const
|
||||
{
|
||||
if (isDynamicallySized())
|
||||
return 32;
|
||||
// Array elements are always padded.
|
||||
bigint size = bigint(length()) * (isByteArray() ? 1 : baseType()->calldataEncodedSize(true));
|
||||
solAssert(!isDynamicallySized(), "");
|
||||
bigint size = bigint(length()) * calldataStride();
|
||||
if (_padded)
|
||||
size = ((size + 31) / 32) * 32;
|
||||
return size;
|
||||
@ -1663,7 +1661,20 @@ bigint ArrayType::unlimitedCalldataEncodedSize(bool _padded) const
|
||||
|
||||
unsigned ArrayType::calldataEncodedSize(bool _padded) const
|
||||
{
|
||||
bigint size = unlimitedCalldataEncodedSize(_padded);
|
||||
solAssert(!isDynamicallyEncoded(), "");
|
||||
bigint size = unlimitedStaticCalldataSize(_padded);
|
||||
solAssert(size <= numeric_limits<unsigned>::max(), "Array size does not fit unsigned.");
|
||||
return unsigned(size);
|
||||
}
|
||||
|
||||
unsigned ArrayType::calldataEncodedTailSize() const
|
||||
{
|
||||
solAssert(isDynamicallyEncoded(), "");
|
||||
if (isDynamicallySized())
|
||||
// We do not know the dynamic length itself, but at least the uint256 containing the
|
||||
// length must still be present.
|
||||
return 32;
|
||||
bigint size = unlimitedStaticCalldataSize(false);
|
||||
solAssert(size <= numeric_limits<unsigned>::max(), "Array size does not fit unsigned.");
|
||||
return unsigned(size);
|
||||
}
|
||||
@ -1832,10 +1843,11 @@ TypeResult ArrayType::interfaceType(bool _inLibrary) const
|
||||
return result;
|
||||
}
|
||||
|
||||
u256 ArrayType::memorySize() const
|
||||
u256 ArrayType::memoryDataSize() const
|
||||
{
|
||||
solAssert(!isDynamicallySized(), "");
|
||||
solAssert(m_location == DataLocation::Memory, "");
|
||||
solAssert(!isByteArray(), "");
|
||||
bigint size = bigint(m_length) * m_baseType->memoryHeadSize();
|
||||
solAssert(size <= numeric_limits<unsigned>::max(), "Array size does not fit u256.");
|
||||
return u256(size);
|
||||
@ -1992,19 +2004,32 @@ bool StructType::operator==(Type const& _other) const
|
||||
return ReferenceType::operator==(other) && other.m_struct == m_struct;
|
||||
}
|
||||
|
||||
|
||||
unsigned StructType::calldataEncodedSize(bool) const
|
||||
{
|
||||
solAssert(!isDynamicallyEncoded(), "");
|
||||
|
||||
unsigned size = 0;
|
||||
for (auto const& member: members(nullptr))
|
||||
if (!member.type->canLiveOutsideStorage())
|
||||
return 0;
|
||||
else
|
||||
{
|
||||
solAssert(member.type->canLiveOutsideStorage(), "");
|
||||
// Struct members are always padded.
|
||||
unsigned memberSize = member.type->calldataEncodedSize(true);
|
||||
if (memberSize == 0)
|
||||
return 0;
|
||||
size += memberSize;
|
||||
size += member.type->calldataEncodedSize();
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
|
||||
unsigned StructType::calldataEncodedTailSize() const
|
||||
{
|
||||
solAssert(isDynamicallyEncoded(), "");
|
||||
|
||||
unsigned size = 0;
|
||||
for (auto const& member: members(nullptr))
|
||||
{
|
||||
solAssert(member.type->canLiveOutsideStorage(), "");
|
||||
// Struct members are always padded.
|
||||
size += member.type->calldataHeadSize();
|
||||
}
|
||||
return size;
|
||||
}
|
||||
@ -2017,12 +2042,8 @@ unsigned StructType::calldataOffsetOfMember(std::string const& _member) const
|
||||
solAssert(member.type->canLiveOutsideStorage(), "");
|
||||
if (member.name == _member)
|
||||
return offset;
|
||||
{
|
||||
// Struct members are always padded.
|
||||
unsigned memberSize = member.type->calldataEncodedSize(true);
|
||||
solAssert(memberSize != 0, "");
|
||||
offset += memberSize;
|
||||
}
|
||||
offset += member.type->calldataHeadSize();
|
||||
}
|
||||
solAssert(false, "Struct member not found.");
|
||||
}
|
||||
@ -2040,7 +2061,7 @@ bool StructType::isDynamicallyEncoded() const
|
||||
return false;
|
||||
}
|
||||
|
||||
u256 StructType::memorySize() const
|
||||
u256 StructType::memoryDataSize() const
|
||||
{
|
||||
u256 size;
|
||||
for (auto const& t: memoryMemberTypes())
|
||||
|
@ -208,16 +208,32 @@ public:
|
||||
virtual bool operator==(Type const& _other) const { return category() == _other.category(); }
|
||||
virtual bool operator!=(Type const& _other) const { return !this->operator ==(_other); }
|
||||
|
||||
/// @returns number of bytes used by this type when encoded for CALL. If it is a dynamic type,
|
||||
/// returns the size of the pointer (usually 32). Returns 0 if the type cannot be encoded
|
||||
/// in calldata.
|
||||
/// @returns number of bytes used by this type when encoded for CALL. Cannot be used for
|
||||
/// dynamically encoded types.
|
||||
/// Always returns a value greater than zero and throws if the type cannot be encoded in calldata
|
||||
/// (or is dynamically encoded).
|
||||
/// If @a _padded then it is assumed that each element is padded to a multiple of 32 bytes.
|
||||
virtual unsigned calldataEncodedSize(bool _padded) const { (void)_padded; return 0; }
|
||||
virtual unsigned calldataEncodedSize(bool _padded) const { (void)_padded; solAssert(false, ""); }
|
||||
/// Convenience version of @see calldataEncodedSize(bool)
|
||||
unsigned calldataEncodedSize() const { return calldataEncodedSize(true); }
|
||||
/// @returns the distance between two elements of this type in a calldata array, tuple or struct.
|
||||
/// For statically encoded types this is the same as calldataEncodedSize(true).
|
||||
/// For dynamically encoded types this is the distance between two tail pointers, i.e. 32.
|
||||
/// Always returns a value greater than zero and throws if the type cannot be encoded in calldata.
|
||||
unsigned calldataHeadSize() const { return isDynamicallyEncoded() ? 32 : calldataEncodedSize(true); }
|
||||
/// @returns the (minimal) size of the calldata tail for this type. Can only be used for
|
||||
/// dynamically encoded types. For dynamically-sized arrays this is 32 (the size of the length),
|
||||
/// for statically-sized, but dynamically encoded arrays this is 32*length(), for structs
|
||||
/// this is the sum of the calldataHeadSize's of its members.
|
||||
/// Always returns a value greater than zero and throws if the type cannot be encoded in calldata
|
||||
/// (or is not dynamically encoded).
|
||||
virtual unsigned calldataEncodedTailSize() const { solAssert(false, ""); }
|
||||
/// @returns the size of this data type in bytes when stored in memory. For memory-reference
|
||||
/// types, this is the size of the memory pointer.
|
||||
virtual unsigned memoryHeadSize() const { return calldataEncodedSize(); }
|
||||
/// Convenience version of @see calldataEncodedSize(bool)
|
||||
unsigned calldataEncodedSize() const { return calldataEncodedSize(true); }
|
||||
/// @returns the size of this data type in bytes when stored in memory. For memory-reference
|
||||
/// types, this is the size of the actual data area, if it is statically-sized.
|
||||
virtual u256 memoryDataSize() const { return calldataEncodedSize(); }
|
||||
/// @returns true if the type is a dynamic array
|
||||
virtual bool isDynamicallySized() const { return false; }
|
||||
/// @returns true if the type is dynamically encoded in the ABI
|
||||
@ -634,6 +650,10 @@ public:
|
||||
return nullptr;
|
||||
}
|
||||
unsigned memoryHeadSize() const override { return 32; }
|
||||
u256 memoryDataSize() const override = 0;
|
||||
|
||||
unsigned calldataEncodedSize(bool) const override = 0;
|
||||
unsigned calldataEncodedTailSize() const override = 0;
|
||||
|
||||
/// @returns a copy of this type with location (recursively) changed to @a _location,
|
||||
/// whereas isPointer is only shallowly changed - the deep copy is always a bound reference.
|
||||
@ -701,7 +721,8 @@ public:
|
||||
BoolResult isExplicitlyConvertibleTo(Type const& _convertTo) const override;
|
||||
std::string richIdentifier() const override;
|
||||
bool operator==(Type const& _other) const override;
|
||||
unsigned calldataEncodedSize(bool _padded) const override;
|
||||
unsigned calldataEncodedSize(bool) const override;
|
||||
unsigned calldataEncodedTailSize() const override;
|
||||
bool isDynamicallySized() const override { return m_hasDynamicLength; }
|
||||
bool isDynamicallyEncoded() const override;
|
||||
u256 storageSize() const override;
|
||||
@ -724,12 +745,12 @@ public:
|
||||
bool isString() const { return m_arrayKind == ArrayKind::String; }
|
||||
Type const* baseType() const { solAssert(!!m_baseType, ""); return m_baseType; }
|
||||
u256 const& length() const { return m_length; }
|
||||
u256 memorySize() const;
|
||||
u256 memoryDataSize() const override;
|
||||
|
||||
std::unique_ptr<ReferenceType> copyForLocation(DataLocation _location, bool _isPointer) const override;
|
||||
|
||||
/// The offset to advance in calldata to move from one array element to the next.
|
||||
unsigned calldataStride() const { return isByteArray() ? 1 : m_baseType->calldataEncodedSize(); }
|
||||
unsigned calldataStride() const { return isByteArray() ? 1 : m_baseType->calldataHeadSize(); }
|
||||
/// The offset to advance in memory to move from one array element to the next.
|
||||
unsigned memoryStride() const { return isByteArray() ? 1 : m_baseType->memoryHeadSize(); }
|
||||
/// The offset to advance in storage to move from one array element to the next.
|
||||
@ -741,7 +762,7 @@ private:
|
||||
/// String is interpreted as a subtype of Bytes.
|
||||
enum class ArrayKind { Ordinary, Bytes, String };
|
||||
|
||||
bigint unlimitedCalldataEncodedSize(bool _padded) const;
|
||||
bigint unlimitedStaticCalldataSize(bool _padded) const;
|
||||
|
||||
///< Byte arrays ("bytes") and strings have different semantics from ordinary arrays.
|
||||
ArrayKind m_arrayKind = ArrayKind::Ordinary;
|
||||
@ -829,9 +850,10 @@ public:
|
||||
BoolResult isImplicitlyConvertibleTo(Type const& _convertTo) const override;
|
||||
std::string richIdentifier() const override;
|
||||
bool operator==(Type const& _other) const override;
|
||||
unsigned calldataEncodedSize(bool _padded) const override;
|
||||
unsigned calldataEncodedSize(bool) const override;
|
||||
unsigned calldataEncodedTailSize() const override;
|
||||
bool isDynamicallyEncoded() const override;
|
||||
u256 memorySize() const;
|
||||
u256 memoryDataSize() const override;
|
||||
u256 storageSize() const override;
|
||||
bool canLiveOutsideStorage() const override { return true; }
|
||||
std::string toString(bool _short) const override;
|
||||
|
@ -88,7 +88,7 @@ string ABIFunctions::tupleEncoder(
|
||||
elementTempl("pos", to_string(headPos));
|
||||
elementTempl("abiEncode", abiEncodingFunction(*_givenTypes[i], *_targetTypes[i], options));
|
||||
encodeElements += elementTempl.render();
|
||||
headPos += dynamic ? 0x20 : _targetTypes[i]->calldataEncodedSize();
|
||||
headPos += _targetTypes[i]->calldataHeadSize();
|
||||
stackPos += sizeOnStack;
|
||||
}
|
||||
solAssert(headPos == headSize_, "");
|
||||
@ -225,7 +225,7 @@ string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory)
|
||||
elementTempl("pos", to_string(headPos));
|
||||
elementTempl("abiDecode", abiDecodingFunction(*_types[i], _fromMemory, true));
|
||||
decodeElements += elementTempl.render();
|
||||
headPos += dynamic ? 0x20 : decodingTypes[i]->calldataEncodedSize();
|
||||
headPos += decodingTypes[i]->calldataHeadSize();
|
||||
}
|
||||
templ("valueReturnParams", boost::algorithm::join(valueReturnParams, ", "));
|
||||
templ("arrow", valueReturnParams.empty() ? "" : "->");
|
||||
@ -691,6 +691,7 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray(
|
||||
// Multiple items per slot
|
||||
solAssert(_from.baseType()->storageBytes() <= 16, "");
|
||||
solAssert(!_from.baseType()->isDynamicallyEncoded(), "");
|
||||
solAssert(!_to.baseType()->isDynamicallyEncoded(), "");
|
||||
solAssert(_from.baseType()->isValueType(), "");
|
||||
bool dynamic = _to.isDynamicallyEncoded();
|
||||
size_t storageBytes = _from.baseType()->storageBytes();
|
||||
@ -715,7 +716,7 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray(
|
||||
let data := sload(srcPtr)
|
||||
<#items>
|
||||
<encodeToMemoryFun>(<extractFromSlot>(data), pos)
|
||||
pos := add(pos, <elementEncodedSize>)
|
||||
pos := add(pos, <stride>)
|
||||
</items>
|
||||
srcPtr := add(srcPtr, 1)
|
||||
}
|
||||
@ -726,7 +727,7 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray(
|
||||
<#items>
|
||||
if <inRange> {
|
||||
<encodeToMemoryFun>(<extractFromSlot>(data), pos)
|
||||
pos := add(pos, <elementEncodedSize>)
|
||||
pos := add(pos, <stride>)
|
||||
itemCounter := add(itemCounter, 1)
|
||||
}
|
||||
</items>
|
||||
@ -753,9 +754,7 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray(
|
||||
else
|
||||
templ("useSpill", "0");
|
||||
templ("itemsPerSlot", to_string(itemsPerSlot));
|
||||
// We use padded size because array elements are always padded.
|
||||
string elementEncodedSize = toCompactHexWithPrefix(_to.baseType()->calldataEncodedSize());
|
||||
templ("elementEncodedSize", elementEncodedSize);
|
||||
templ("stride", toCompactHexWithPrefix(_to.calldataStride()));
|
||||
|
||||
EncodingOptions subOptions(_options);
|
||||
subOptions.encodeFunctionFromStack = false;
|
||||
@ -914,7 +913,7 @@ string ABIFunctions::abiEncodingFunctionStruct(
|
||||
);
|
||||
encodeTempl("memberValues", memberValues);
|
||||
encodeTempl("encodingOffset", toCompactHexWithPrefix(encodingOffset));
|
||||
encodingOffset += dynamicMember ? 0x20 : memberTypeTo->calldataEncodedSize();
|
||||
encodingOffset += memberTypeTo->calldataHeadSize();
|
||||
encodeTempl("abiEncode", abiEncodingFunction(*memberTypeFrom, *memberTypeTo, subOptions));
|
||||
encode = encodeTempl.render();
|
||||
}
|
||||
@ -1080,8 +1079,8 @@ string ABIFunctions::abiDecodingFunctionValueType(Type const& _type, bool _fromM
|
||||
solAssert(decodingType, "");
|
||||
solAssert(decodingType->sizeOnStack() == 1, "");
|
||||
solAssert(decodingType->isValueType(), "");
|
||||
solAssert(decodingType->calldataEncodedSize() == 32, "");
|
||||
solAssert(!decodingType->isDynamicallyEncoded(), "");
|
||||
solAssert(decodingType->calldataEncodedSize() == 32, "");
|
||||
|
||||
string functionName =
|
||||
"abi_decode_" +
|
||||
@ -1135,7 +1134,7 @@ string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _from
|
||||
let elementPos := <retrieveElementPos>
|
||||
mstore(dst, <decodingFun>(elementPos, end))
|
||||
dst := add(dst, 0x20)
|
||||
src := add(src, <baseEncodedSize>)
|
||||
src := add(src, <stride>)
|
||||
}
|
||||
}
|
||||
)"
|
||||
@ -1145,6 +1144,8 @@ string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _from
|
||||
templ("retrieveLength", !_type.isDynamicallySized() ? toCompactHexWithPrefix(_type.length()) : load + "(offset)");
|
||||
templ("allocate", m_utils.allocationFunction());
|
||||
templ("allocationSize", m_utils.arrayAllocationSizeFunction(_type));
|
||||
string calldataStride = toCompactHexWithPrefix(_type.calldataStride());
|
||||
templ("stride", calldataStride);
|
||||
if (_type.isDynamicallySized())
|
||||
templ("storeLength", "mstore(array, length) offset := add(offset, 0x20) dst := add(dst, 0x20)");
|
||||
else
|
||||
@ -1153,14 +1154,11 @@ string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _from
|
||||
{
|
||||
templ("staticBoundsCheck", "");
|
||||
templ("retrieveElementPos", "add(offset, " + load + "(src))");
|
||||
templ("baseEncodedSize", "0x20");
|
||||
}
|
||||
else
|
||||
{
|
||||
string baseEncodedSize = toCompactHexWithPrefix(_type.baseType()->calldataEncodedSize());
|
||||
templ("staticBoundsCheck", "if gt(add(src, mul(length, " + baseEncodedSize + ")), end) { revert(0, 0) }");
|
||||
templ("staticBoundsCheck", "if gt(add(src, mul(length, " + calldataStride + ")), end) { revert(0, 0) }");
|
||||
templ("retrieveElementPos", "src");
|
||||
templ("baseEncodedSize", baseEncodedSize);
|
||||
}
|
||||
templ("decodingFun", abiDecodingFunction(*_type.baseType(), _fromMemory, false));
|
||||
return templ.render();
|
||||
@ -1172,8 +1170,8 @@ string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type)
|
||||
solAssert(_type.dataStoredIn(DataLocation::CallData), "");
|
||||
if (!_type.isDynamicallySized())
|
||||
solAssert(_type.length() < u256("0xffffffffffffffff"), "");
|
||||
solAssert(_type.baseType()->calldataEncodedSize() > 0, "");
|
||||
solAssert(_type.baseType()->calldataEncodedSize() < u256("0xffffffffffffffff"), "");
|
||||
solAssert(_type.calldataStride() > 0, "");
|
||||
solAssert(_type.calldataStride() < u256("0xffffffffffffffff"), "");
|
||||
|
||||
string functionName =
|
||||
"abi_decode_" +
|
||||
@ -1188,7 +1186,7 @@ string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type)
|
||||
length := calldataload(offset)
|
||||
if gt(length, 0xffffffffffffffff) { revert(0, 0) }
|
||||
arrayPos := add(offset, 0x20)
|
||||
if gt(add(arrayPos, mul(length, <baseEncodedSize>)), end) { revert(0, 0) }
|
||||
if gt(add(arrayPos, mul(length, <stride>)), end) { revert(0, 0) }
|
||||
}
|
||||
)";
|
||||
else
|
||||
@ -1196,13 +1194,13 @@ string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type)
|
||||
// <readableTypeName>
|
||||
function <functionName>(offset, end) -> arrayPos {
|
||||
arrayPos := offset
|
||||
if gt(add(arrayPos, mul(<length>, <baseEncodedSize>)), end) { revert(0, 0) }
|
||||
if gt(add(arrayPos, mul(<length>, <stride>)), end) { revert(0, 0) }
|
||||
}
|
||||
)";
|
||||
Whiskers w{templ};
|
||||
w("functionName", functionName);
|
||||
w("readableTypeName", _type.toString(true));
|
||||
w("baseEncodedSize", toCompactHexWithPrefix(_type.isByteArray() ? 1 : _type.baseType()->calldataEncodedSize()));
|
||||
w("stride", toCompactHexWithPrefix(_type.calldataStride()));
|
||||
if (!_type.isDynamicallySized())
|
||||
w("length", toCompactHexWithPrefix(_type.length()));
|
||||
return w.render();
|
||||
@ -1246,7 +1244,6 @@ string ABIFunctions::abiDecodingFunctionByteArray(ArrayType const& _type, bool _
|
||||
string ABIFunctions::abiDecodingFunctionCalldataStruct(StructType const& _type)
|
||||
{
|
||||
solAssert(_type.dataStoredIn(DataLocation::CallData), "");
|
||||
solAssert(_type.calldataEncodedSize(true) != 0, "");
|
||||
string functionName =
|
||||
"abi_decode_" +
|
||||
_type.identifier();
|
||||
@ -1261,7 +1258,7 @@ string ABIFunctions::abiDecodingFunctionCalldataStruct(StructType const& _type)
|
||||
)"};
|
||||
w("functionName", functionName);
|
||||
w("readableTypeName", _type.toString(true));
|
||||
w("minimumSize", to_string(_type.calldataEncodedSize(true)));
|
||||
w("minimumSize", to_string(_type.isDynamicallyEncoded() ? _type.calldataEncodedTailSize() : _type.calldataEncodedSize(true)));
|
||||
return w.render();
|
||||
});
|
||||
}
|
||||
@ -1291,8 +1288,8 @@ string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fr
|
||||
templ("functionName", functionName);
|
||||
templ("readableTypeName", _type.toString(true));
|
||||
templ("allocate", m_utils.allocationFunction());
|
||||
solAssert(_type.memorySize() < u256("0xffffffffffffffff"), "");
|
||||
templ("memorySize", toCompactHexWithPrefix(_type.memorySize()));
|
||||
solAssert(_type.memoryDataSize() < u256("0xffffffffffffffff"), "");
|
||||
templ("memorySize", toCompactHexWithPrefix(_type.memoryDataSize()));
|
||||
size_t headPos = 0;
|
||||
vector<map<string, string>> members;
|
||||
for (auto const& member: _type.members(nullptr))
|
||||
@ -1322,7 +1319,7 @@ string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fr
|
||||
members.push_back({});
|
||||
members.back()["decode"] = memberTempl.render();
|
||||
members.back()["memberName"] = member.name;
|
||||
headPos += dynamic ? 0x20 : decodingType->calldataEncodedSize();
|
||||
headPos += decodingType->calldataHeadSize();
|
||||
}
|
||||
templ("members", members);
|
||||
templ("minimumSize", toCompactHexWithPrefix(headPos));
|
||||
@ -1376,8 +1373,8 @@ string ABIFunctions::calldataAccessFunction(Type const& _type)
|
||||
return createFunction(functionName, [&]() {
|
||||
if (_type.isDynamicallyEncoded())
|
||||
{
|
||||
unsigned int baseEncodedSize = _type.calldataEncodedSize();
|
||||
solAssert(baseEncodedSize > 1, "");
|
||||
unsigned int tailSize = _type.calldataEncodedTailSize();
|
||||
solAssert(tailSize > 1, "");
|
||||
Whiskers w(R"(
|
||||
function <functionName>(base_ref, ptr) -> <return> {
|
||||
let rel_offset_of_tail := calldataload(ptr)
|
||||
@ -1390,13 +1387,12 @@ string ABIFunctions::calldataAccessFunction(Type const& _type)
|
||||
{
|
||||
auto const* arrayType = dynamic_cast<ArrayType const*>(&_type);
|
||||
solAssert(!!arrayType, "");
|
||||
unsigned int calldataStride = arrayType->calldataStride();
|
||||
w("handleLength", Whiskers(R"(
|
||||
length := calldataload(value)
|
||||
value := add(value, 0x20)
|
||||
if gt(length, 0xffffffffffffffff) { revert(0, 0) }
|
||||
if sgt(base_ref, sub(calldatasize(), mul(length, <calldataStride>))) { revert(0, 0) }
|
||||
)")("calldataStride", toCompactHexWithPrefix(calldataStride)).render());
|
||||
)")("calldataStride", toCompactHexWithPrefix(arrayType->calldataStride())).render());
|
||||
w("return", "value, length");
|
||||
}
|
||||
else
|
||||
@ -1404,7 +1400,7 @@ string ABIFunctions::calldataAccessFunction(Type const& _type)
|
||||
w("handleLength", "");
|
||||
w("return", "value");
|
||||
}
|
||||
w("neededLength", toCompactHexWithPrefix(baseEncodedSize));
|
||||
w("neededLength", toCompactHexWithPrefix(tailSize));
|
||||
w("functionName", functionName);
|
||||
return w.render();
|
||||
}
|
||||
@ -1483,12 +1479,7 @@ size_t ABIFunctions::headSize(TypePointers const& _targetTypes)
|
||||
{
|
||||
size_t headSize = 0;
|
||||
for (auto const& t: _targetTypes)
|
||||
{
|
||||
if (t->isDynamicallyEncoded())
|
||||
headSize += 0x20;
|
||||
else
|
||||
headSize += t->calldataEncodedSize();
|
||||
}
|
||||
headSize += t->calldataHeadSize();
|
||||
|
||||
return headSize;
|
||||
}
|
||||
|
@ -243,7 +243,7 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons
|
||||
else if (_sourceType.location() == DataLocation::Memory)
|
||||
_context << sourceBaseType->memoryHeadSize();
|
||||
else
|
||||
_context << sourceBaseType->calldataEncodedSize(true);
|
||||
_context << sourceBaseType->calldataHeadSize();
|
||||
_context
|
||||
<< Instruction::ADD
|
||||
<< swapInstruction(2 + byteOffsetSize);
|
||||
@ -294,20 +294,13 @@ void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWord
|
||||
"Nested dynamic arrays not implemented here."
|
||||
);
|
||||
CompilerUtils utils(m_context);
|
||||
unsigned baseSize = 1;
|
||||
if (!_sourceType.isByteArray())
|
||||
{
|
||||
// We always pad the elements, regardless of _padToWordBoundaries.
|
||||
baseSize = _sourceType.baseType()->calldataEncodedSize();
|
||||
solAssert(baseSize >= 0x20, "");
|
||||
}
|
||||
|
||||
if (_sourceType.location() == DataLocation::CallData)
|
||||
{
|
||||
if (!_sourceType.isDynamicallySized())
|
||||
m_context << _sourceType.length();
|
||||
if (baseSize > 1)
|
||||
m_context << u256(baseSize) << Instruction::MUL;
|
||||
if (!_sourceType.isByteArray())
|
||||
convertLengthToSize(_sourceType);
|
||||
|
||||
string routine = "calldatacopy(target, source, len)\n";
|
||||
if (_padToWordBoundaries)
|
||||
@ -358,15 +351,14 @@ void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWord
|
||||
m_context << Instruction::SWAP1 << u256(32) << Instruction::ADD;
|
||||
m_context << Instruction::SWAP1;
|
||||
}
|
||||
// convert length to size
|
||||
if (baseSize > 1)
|
||||
m_context << u256(baseSize) << Instruction::MUL;
|
||||
if (!_sourceType.isByteArray())
|
||||
convertLengthToSize(_sourceType);
|
||||
// stack: <target> <source> <size>
|
||||
m_context << Instruction::DUP1 << Instruction::DUP4 << Instruction::DUP4;
|
||||
// We can resort to copying full 32 bytes only if
|
||||
// - the length is known to be a multiple of 32 or
|
||||
// - we will pad to full 32 bytes later anyway.
|
||||
if (((baseSize % 32) == 0) || _padToWordBoundaries)
|
||||
if (!_sourceType.isByteArray() || _padToWordBoundaries)
|
||||
utils.memoryCopy32();
|
||||
else
|
||||
utils.memoryCopy();
|
||||
@ -374,11 +366,8 @@ void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWord
|
||||
m_context << Instruction::SWAP1 << Instruction::POP;
|
||||
// stack: <target> <size>
|
||||
|
||||
bool paddingNeeded = false;
|
||||
if (_sourceType.isDynamicallySized())
|
||||
paddingNeeded = _padToWordBoundaries && ((baseSize % 32) != 0);
|
||||
else
|
||||
paddingNeeded = _padToWordBoundaries && (((_sourceType.length() * baseSize) % 32) != 0);
|
||||
bool paddingNeeded = _padToWordBoundaries && _sourceType.isByteArray();
|
||||
|
||||
if (paddingNeeded)
|
||||
{
|
||||
// stack: <target> <size>
|
||||
@ -455,10 +444,10 @@ void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWord
|
||||
m_context.appendJumpTo(loopEnd);
|
||||
m_context << longByteArray;
|
||||
}
|
||||
// compute memory end offset
|
||||
if (baseSize > 1)
|
||||
else
|
||||
// convert length to memory size
|
||||
m_context << u256(baseSize) << Instruction::MUL;
|
||||
m_context << _sourceType.baseType()->memoryHeadSize() << Instruction::MUL;
|
||||
|
||||
m_context << Instruction::DUP3 << Instruction::ADD << Instruction::SWAP2;
|
||||
if (_sourceType.isDynamicallySized())
|
||||
{
|
||||
@ -515,7 +504,12 @@ void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWord
|
||||
// stack here: memory_end_offset storage_data_offset [storage_byte_offset] memory_offset
|
||||
if (haveByteOffset)
|
||||
m_context << Instruction::SWAP1 << Instruction::POP;
|
||||
if (_padToWordBoundaries && baseSize % 32 != 0)
|
||||
if (!_sourceType.isByteArray())
|
||||
{
|
||||
solAssert(_sourceType.calldataStride() % 32 == 0, "");
|
||||
solAssert(_sourceType.memoryStride() % 32 == 0, "");
|
||||
}
|
||||
if (_padToWordBoundaries && _sourceType.isByteArray())
|
||||
{
|
||||
// memory_end_offset - start is the actual length (we want to compute the ceil of).
|
||||
// memory_offset - start is its next multiple of 32, but it might be off by 32.
|
||||
@ -987,9 +981,9 @@ void ArrayUtils::convertLengthToSize(ArrayType const& _arrayType, bool _pad) con
|
||||
if (!_arrayType.isByteArray())
|
||||
{
|
||||
if (_arrayType.location() == DataLocation::Memory)
|
||||
m_context << _arrayType.baseType()->memoryHeadSize();
|
||||
m_context << _arrayType.memoryStride();
|
||||
else
|
||||
m_context << _arrayType.baseType()->calldataEncodedSize();
|
||||
m_context << _arrayType.calldataStride();
|
||||
m_context << Instruction::MUL;
|
||||
}
|
||||
else if (_pad)
|
||||
@ -1067,10 +1061,7 @@ void ArrayUtils::accessIndex(ArrayType const& _arrayType, bool _doBoundsCheck, b
|
||||
case DataLocation::CallData:
|
||||
if (!_arrayType.isByteArray())
|
||||
{
|
||||
if (_arrayType.baseType()->isDynamicallyEncoded())
|
||||
m_context << u256(0x20);
|
||||
else
|
||||
m_context << _arrayType.baseType()->calldataEncodedSize();
|
||||
m_context << _arrayType.calldataStride();
|
||||
m_context << Instruction::MUL;
|
||||
}
|
||||
// stack: <base_ref> <index * size>
|
||||
|
@ -98,16 +98,17 @@ void CompilerUtils::revertWithStringData(Type const& _argumentType)
|
||||
void CompilerUtils::accessCalldataTail(Type const& _type)
|
||||
{
|
||||
solAssert(_type.dataStoredIn(DataLocation::CallData), "");
|
||||
solAssert(_type.isDynamicallyEncoded(), "");
|
||||
|
||||
unsigned int baseEncodedSize = _type.calldataEncodedSize();
|
||||
solAssert(baseEncodedSize > 1, "");
|
||||
unsigned int tailSize = _type.calldataEncodedTailSize();
|
||||
solAssert(tailSize > 1, "");
|
||||
|
||||
// returns the absolute offset of the tail in "base_ref"
|
||||
m_context.appendInlineAssembly(Whiskers(R"({
|
||||
let rel_offset_of_tail := calldataload(ptr_to_tail)
|
||||
if iszero(slt(rel_offset_of_tail, sub(sub(calldatasize(), base_ref), sub(<neededLength>, 1)))) { revert(0, 0) }
|
||||
base_ref := add(base_ref, rel_offset_of_tail)
|
||||
})")("neededLength", toCompactHexWithPrefix(baseEncodedSize)).render(), {"base_ref", "ptr_to_tail"});
|
||||
})")("neededLength", toCompactHexWithPrefix(tailSize)).render(), {"base_ref", "ptr_to_tail"});
|
||||
// stack layout: <absolute_offset_of_tail> <garbage>
|
||||
|
||||
if (!_type.isDynamicallySized())
|
||||
@ -170,7 +171,7 @@ void CompilerUtils::loadFromMemoryDynamic(
|
||||
solAssert(!_fromCalldata, "");
|
||||
solAssert(_padToWordBoundaries, "");
|
||||
if (_keepUpdatedMemoryOffset)
|
||||
m_context << arrayType->memorySize() << Instruction::ADD;
|
||||
m_context << arrayType->memoryDataSize() << Instruction::ADD;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -251,7 +252,7 @@ void CompilerUtils::abiDecode(TypePointers const& _typeParameters, bool _fromMem
|
||||
//@todo this does not yet support nested dynamic arrays
|
||||
size_t encodedSize = 0;
|
||||
for (auto const& t: _typeParameters)
|
||||
encodedSize += t->decodingType()->calldataEncodedSize(true);
|
||||
encodedSize += t->decodingType()->calldataHeadSize();
|
||||
m_context.appendInlineAssembly("{ if lt(len, " + to_string(encodedSize) + ") { revert(0, 0) } }", {"len"});
|
||||
|
||||
m_context << Instruction::DUP2 << Instruction::ADD;
|
||||
@ -321,7 +322,7 @@ void CompilerUtils::abiDecode(TypePointers const& _typeParameters, bool _fromMem
|
||||
// Size has already been checked for this one.
|
||||
moveIntoStack(2);
|
||||
m_context << Instruction::DUP3;
|
||||
m_context << u256(arrayType.calldataEncodedSize(true)) << Instruction::ADD;
|
||||
m_context << u256(arrayType.calldataHeadSize()) << Instruction::ADD;
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -358,7 +359,7 @@ void CompilerUtils::abiDecode(TypePointers const& _typeParameters, bool _fromMem
|
||||
// size has already been checked
|
||||
// stack: input_end base_offset data_offset
|
||||
m_context << Instruction::DUP1;
|
||||
m_context << u256(calldataType->calldataEncodedSize()) << Instruction::ADD;
|
||||
m_context << u256(calldataType->calldataHeadSize()) << Instruction::ADD;
|
||||
}
|
||||
if (arrayType.location() == DataLocation::Memory)
|
||||
{
|
||||
@ -588,11 +589,6 @@ void CompilerUtils::zeroInitialiseMemoryArray(ArrayType const& _type)
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Potential optimization:
|
||||
// When we create a new multi-dimensional dynamic array, each element
|
||||
// is initialized to an empty array. It actually does not hurt
|
||||
// to re-use exactly the same empty array for all elements. Currently,
|
||||
// a new one is created each time.
|
||||
auto repeat = m_context.newTag();
|
||||
m_context << repeat;
|
||||
pushZeroValue(*_type.baseType());
|
||||
@ -1014,7 +1010,7 @@ void CompilerUtils::convertType(
|
||||
{
|
||||
CompilerUtils utils(_context);
|
||||
// stack: <source ref>
|
||||
utils.allocateMemory(typeOnStack->memorySize());
|
||||
utils.allocateMemory(typeOnStack->memoryDataSize());
|
||||
_context << Instruction::SWAP1 << Instruction::DUP2;
|
||||
// stack: <memory ptr> <source ref> <memory ptr>
|
||||
for (auto const& member: typeOnStack->members(nullptr))
|
||||
@ -1197,7 +1193,8 @@ void CompilerUtils::pushZeroValue(Type const& _type)
|
||||
1,
|
||||
[type](CompilerContext& _context) {
|
||||
CompilerUtils utils(_context);
|
||||
utils.allocateMemory(max(32u, type->calldataEncodedSize()));
|
||||
|
||||
utils.allocateMemory(max<u256>(32u, type->memoryDataSize()));
|
||||
_context << Instruction::DUP1;
|
||||
|
||||
if (auto structType = dynamic_cast<StructType const*>(type))
|
||||
@ -1364,6 +1361,8 @@ void CompilerUtils::storeStringData(bytesConstRef _data)
|
||||
|
||||
unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCalldata, bool _padToWords)
|
||||
{
|
||||
solAssert(_type.isValueType(), "");
|
||||
|
||||
unsigned numBytes = _type.calldataEncodedSize(_padToWords);
|
||||
bool isExternalFunctionType = false;
|
||||
if (auto const* funType = dynamic_cast<FunctionType const*>(&_type))
|
||||
@ -1436,6 +1435,8 @@ unsigned CompilerUtils::prepareMemoryStore(Type const& _type, bool _padToWords)
|
||||
"Memory store of types with stack size != 1 not allowed (Type: " + _type.toString(true) + ")."
|
||||
);
|
||||
|
||||
solAssert(!_type.isDynamicallyEncoded(), "");
|
||||
|
||||
unsigned numBytes = _type.calldataEncodedSize(_padToWords);
|
||||
|
||||
solAssert(
|
||||
|
@ -317,7 +317,7 @@ bool ExpressionCompiler::visit(TupleExpression const& _tuple)
|
||||
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.memorySize()));
|
||||
utils().allocateMemory(max(u256(32u), arrayType.memoryDataSize()));
|
||||
m_context << Instruction::DUP1;
|
||||
|
||||
for (auto const& component: _tuple.components())
|
||||
@ -526,7 +526,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
||||
TypeType const& type = dynamic_cast<TypeType const&>(*_functionCall.expression().annotation().type);
|
||||
auto const& structType = dynamic_cast<StructType const&>(*type.actualType());
|
||||
|
||||
utils().allocateMemory(max(u256(32u), structType.memorySize()));
|
||||
utils().allocateMemory(max(u256(32u), structType.memoryDataSize()));
|
||||
m_context << Instruction::DUP1;
|
||||
|
||||
for (unsigned i = 0; i < arguments.size(); ++i)
|
||||
|
@ -652,11 +652,11 @@ string YulUtilFunctions::arrayConvertLengthToSize(ArrayType const& _type)
|
||||
<?byteArray>
|
||||
size := length
|
||||
<!byteArray>
|
||||
size := <mul>(length, <elementSize>)
|
||||
size := <mul>(length, <stride>)
|
||||
</byteArray>
|
||||
})")
|
||||
("functionName", functionName)
|
||||
("elementSize", to_string(_type.location() == DataLocation::Memory ? baseType.memoryHeadSize() : baseType.calldataEncodedSize()))
|
||||
("stride", to_string(_type.location() == DataLocation::Memory ? _type.memoryStride() : _type.calldataStride()))
|
||||
("byteArray", _type.isByteArray())
|
||||
("mul", overflowCheckedIntMulFunction(*TypeProvider::uint256()))
|
||||
.render();
|
||||
@ -812,10 +812,7 @@ string YulUtilFunctions::nextArrayElementFunction(ArrayType const& _type)
|
||||
}
|
||||
case DataLocation::CallData:
|
||||
{
|
||||
u256 size =
|
||||
_type.baseType()->isDynamicallyEncoded() ?
|
||||
32 :
|
||||
_type.baseType()->calldataEncodedSize();
|
||||
u256 size = _type.calldataStride();
|
||||
solAssert(size >= 32 && size % 32 == 0, "");
|
||||
templ("advance", toCompactHexWithPrefix(size));
|
||||
break;
|
||||
|
@ -0,0 +1,11 @@
|
||||
pragma experimental ABIEncoderV2;
|
||||
contract C {
|
||||
function f(uint256[][2][] calldata x) external returns (uint256) {
|
||||
x[0]; // trigger bounds checks
|
||||
return 23;
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// f(uint256[][2][]): 0x20, 0x01, 0x20, 0x40, 0x60, 0x00, 0x00 -> 23 # this is the common encoding for x.length == 1 && x[0][0].length == 0 && x[0][1].length == 0 #
|
||||
// f(uint256[][2][]): 0x20, 0x01, 0x20, 0x00, 0x00 -> 23 # exotic, but still valid encoding #
|
||||
// f(uint256[][2][]): 0x20, 0x01, 0x20, 0x00 -> FAILURE # invalid (too short) encoding, but no failure due to this PR #
|
@ -0,0 +1,13 @@
|
||||
pragma experimental ABIEncoderV2;
|
||||
contract C {
|
||||
function f(uint256[][2][] calldata x) external returns (uint256) {
|
||||
return 42;
|
||||
}
|
||||
function g(uint256[][2][] calldata x) external returns (uint256) {
|
||||
return this.f(x);
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// g(uint256[][2][]): 0x20, 0x01, 0x20, 0x40, 0x60, 0x00, 0x00 -> 42
|
||||
// g(uint256[][2][]): 0x20, 0x01, 0x20, 0x00, 0x00 -> 42
|
||||
// g(uint256[][2][]): 0x20, 0x01, 0x20, 0x00 -> FAILURE
|
@ -0,0 +1,23 @@
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
contract C {
|
||||
struct A {
|
||||
uint256 a;
|
||||
uint256[] b;
|
||||
}
|
||||
struct B {
|
||||
A a;
|
||||
uint256 b;
|
||||
}
|
||||
function g(B calldata b) external pure returns(uint256) {
|
||||
return b.b;
|
||||
}
|
||||
function f() public view returns(uint256, uint256) {
|
||||
uint256[] memory arr = new uint256[](20);
|
||||
arr[0] = 31; arr[2] = 84;
|
||||
B memory b = B(A(420, arr), 11);
|
||||
return (b.b, this.g(b));
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// f() -> 11, 11
|
Loading…
Reference in New Issue
Block a user