mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #6362 from ethereum/fixABIEncoderV2StorageRead
Fix abi encoder v2 storage read
This commit is contained in:
@@ -2842,6 +2842,14 @@ u256 FunctionType::storageSize() const
|
||||
solAssert(false, "Storage size of non-storable function type requested.");
|
||||
}
|
||||
|
||||
bool FunctionType::leftAligned() const
|
||||
{
|
||||
if (m_kind == Kind::External)
|
||||
return true;
|
||||
else
|
||||
solAssert(false, "Alignment property of non-exportable function type requested.");
|
||||
}
|
||||
|
||||
unsigned FunctionType::storageBytes() const
|
||||
{
|
||||
if (m_kind == Kind::External)
|
||||
|
||||
@@ -236,6 +236,13 @@ public:
|
||||
/// In order to avoid computation at runtime of whether such moving is necessary, structs and
|
||||
/// array data (not each element) always start a new slot.
|
||||
virtual unsigned storageBytes() const { return 32; }
|
||||
/// Returns true if the type is a value type that is left-aligned on the stack with a size of
|
||||
/// storageBytes() bytes. Returns false if the type is a value type that is right-aligned on
|
||||
/// the stack with a size of storageBytes() bytes. Asserts if it is not a value type or the
|
||||
/// encoding is more complicated.
|
||||
/// Signed integers are not considered "more complicated" even though they need to be
|
||||
/// sign-extended.
|
||||
virtual bool leftAligned() const { solAssert(false, "Alignment property of non-value type requested."); }
|
||||
/// Returns true if the type can be stored in storage.
|
||||
virtual bool canBeStored() const { return true; }
|
||||
/// Returns false if the type cannot live outside the storage, i.e. if it includes some mapping.
|
||||
@@ -345,6 +352,7 @@ public:
|
||||
|
||||
unsigned calldataEncodedSize(bool _padded = true) const override { return _padded ? 32 : 160 / 8; }
|
||||
unsigned storageBytes() const override { return 160 / 8; }
|
||||
bool leftAligned() const override { return false; }
|
||||
bool isValueType() const override { return true; }
|
||||
|
||||
MemberList::MemberMap nativeMembers(ContractDefinition const*) const override;
|
||||
@@ -390,6 +398,7 @@ public:
|
||||
|
||||
unsigned calldataEncodedSize(bool _padded = true) const override { return _padded ? 32 : m_bits / 8; }
|
||||
unsigned storageBytes() const override { return m_bits / 8; }
|
||||
bool leftAligned() const override { return false; }
|
||||
bool isValueType() const override { return true; }
|
||||
|
||||
std::string toString(bool _short) const override;
|
||||
@@ -432,6 +441,7 @@ public:
|
||||
|
||||
unsigned calldataEncodedSize(bool _padded = true) const override { return _padded ? 32 : m_totalBits / 8; }
|
||||
unsigned storageBytes() const override { return m_totalBits / 8; }
|
||||
bool leftAligned() const override { return false; }
|
||||
bool isValueType() const override { return true; }
|
||||
|
||||
std::string toString(bool _short) const override;
|
||||
@@ -578,6 +588,7 @@ public:
|
||||
|
||||
unsigned calldataEncodedSize(bool _padded) const override { return _padded && m_bytes > 0 ? 32 : m_bytes; }
|
||||
unsigned storageBytes() const override { return m_bytes; }
|
||||
bool leftAligned() const override { return true; }
|
||||
bool isValueType() const override { return true; }
|
||||
|
||||
std::string toString(bool) const override { return "bytes" + dev::toString(m_bytes); }
|
||||
@@ -604,6 +615,7 @@ public:
|
||||
|
||||
unsigned calldataEncodedSize(bool _padded) const override{ return _padded ? 32 : 1; }
|
||||
unsigned storageBytes() const override { return 1; }
|
||||
bool leftAligned() const override { return false; }
|
||||
bool isValueType() const override { return true; }
|
||||
|
||||
std::string toString(bool) const override { return "bool"; }
|
||||
@@ -775,6 +787,7 @@ public:
|
||||
return encodingType()->calldataEncodedSize(_padded);
|
||||
}
|
||||
unsigned storageBytes() const override { solAssert(!isSuper(), ""); return 20; }
|
||||
bool leftAligned() const override { solAssert(!isSuper(), ""); return false; }
|
||||
bool canLiveOutsideStorage() const override { return !isSuper(); }
|
||||
unsigned sizeOnStack() const override { return m_super ? 0 : 1; }
|
||||
bool isValueType() const override { return !isSuper(); }
|
||||
@@ -897,6 +910,7 @@ public:
|
||||
return encodingType()->calldataEncodedSize(_padded);
|
||||
}
|
||||
unsigned storageBytes() const override;
|
||||
bool leftAligned() const override { return false; }
|
||||
bool canLiveOutsideStorage() const override { return true; }
|
||||
std::string toString(bool _short) const override;
|
||||
std::string canonicalName() const override;
|
||||
@@ -1096,6 +1110,7 @@ public:
|
||||
unsigned calldataEncodedSize(bool _padded) const override;
|
||||
bool canBeStored() const override { return m_kind == Kind::Internal || m_kind == Kind::External; }
|
||||
u256 storageSize() const override;
|
||||
bool leftAligned() const override;
|
||||
unsigned storageBytes() const override;
|
||||
bool isValueType() const override { return true; }
|
||||
bool canLiveOutsideStorage() const override { return m_kind == Kind::Internal || m_kind == Kind::External; }
|
||||
|
||||
@@ -346,6 +346,39 @@ string ABIFunctions::cleanupFunction(Type const& _type, bool _revertOnFailure)
|
||||
});
|
||||
}
|
||||
|
||||
string ABIFunctions::cleanupFromStorageFunction(Type const& _type, bool _splitFunctionTypes)
|
||||
{
|
||||
solAssert(_type.isValueType(), "");
|
||||
solUnimplementedAssert(!_splitFunctionTypes, "");
|
||||
|
||||
string functionName = string("cleanup_from_storage_") + (_splitFunctionTypes ? "split_" : "") + _type.identifier();
|
||||
return createFunction(functionName, [&] {
|
||||
Whiskers templ(R"(
|
||||
function <functionName>(value) -> cleaned {
|
||||
<body>
|
||||
}
|
||||
)");
|
||||
templ("functionName", functionName);
|
||||
|
||||
unsigned storageBytes = _type.storageBytes();
|
||||
if (IntegerType const* type = dynamic_cast<IntegerType const*>(&_type))
|
||||
if (type->isSigned() && storageBytes != 32)
|
||||
{
|
||||
templ("body", "cleaned := signextend(" + to_string(storageBytes - 1) + ", value)");
|
||||
return templ.render();
|
||||
}
|
||||
|
||||
if (storageBytes == 32)
|
||||
templ("body", "cleaned := value");
|
||||
else if (_type.leftAligned())
|
||||
templ("body", "cleaned := " + m_utils.shiftLeftFunction(256 - 8 * storageBytes) + "(value)");
|
||||
else
|
||||
templ("body", "cleaned := and(value, " + toCompactHexWithPrefix((u256(1) << (8 * storageBytes)) - 1) + ")");
|
||||
|
||||
return templ.render();
|
||||
});
|
||||
}
|
||||
|
||||
string ABIFunctions::conversionFunction(Type const& _from, Type const& _to)
|
||||
{
|
||||
string functionName =
|
||||
@@ -778,7 +811,15 @@ string ABIFunctions::abiEncodingFunctionSimpleArray(
|
||||
subOptions.encodeFunctionFromStack = false;
|
||||
subOptions.padded = true;
|
||||
templ("encodeToMemoryFun", abiEncodeAndReturnUpdatedPosFunction(*_from.baseType(), *_to.baseType(), subOptions));
|
||||
templ("arrayElementAccess", inMemory ? "mload(srcPtr)" : _from.baseType()->isValueType() ? "sload(srcPtr)" : "srcPtr" );
|
||||
if (inMemory)
|
||||
templ("arrayElementAccess", "mload(srcPtr)");
|
||||
else if (_from.baseType()->isValueType())
|
||||
{
|
||||
solAssert(_from.dataStoredIn(DataLocation::Storage), "");
|
||||
templ("arrayElementAccess", readFromStorage(*_from.baseType(), 0, false) + "(srcPtr)");
|
||||
}
|
||||
else
|
||||
templ("arrayElementAccess", "srcPtr");
|
||||
templ("nextArrayElement", m_utils.nextArrayElementFunction(_from));
|
||||
return templ.render();
|
||||
});
|
||||
@@ -886,8 +927,9 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray(
|
||||
bool dynamic = _to.isDynamicallyEncoded();
|
||||
size_t storageBytes = _from.baseType()->storageBytes();
|
||||
size_t itemsPerSlot = 32 / storageBytes;
|
||||
// This always writes full slot contents to memory, which might be
|
||||
// more than desired, i.e. it always writes beyond the end of memory.
|
||||
solAssert(itemsPerSlot > 0, "");
|
||||
// The number of elements we need to handle manually after the loop.
|
||||
size_t spill = size_t(_from.length() % itemsPerSlot);
|
||||
Whiskers templ(
|
||||
R"(
|
||||
// <readableTypeNameFrom> -> <readableTypeNameTo>
|
||||
@@ -896,16 +938,31 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray(
|
||||
pos := <storeLength>(pos, length)
|
||||
let originalPos := pos
|
||||
let srcPtr := <dataArea>(value)
|
||||
for { let i := 0 } lt(i, length) { i := add(i, <itemsPerSlot>) }
|
||||
{
|
||||
let itemCounter := 0
|
||||
if <useLoop> {
|
||||
// Run the loop over all full slots
|
||||
for { } lt(add(itemCounter, sub(<itemsPerSlot>, 1)), length)
|
||||
{ itemCounter := add(itemCounter, <itemsPerSlot>) }
|
||||
{
|
||||
let data := sload(srcPtr)
|
||||
<#items>
|
||||
<encodeToMemoryFun>(<extractFromSlot>(data), pos)
|
||||
pos := add(pos, <elementEncodedSize>)
|
||||
</items>
|
||||
srcPtr := add(srcPtr, 1)
|
||||
}
|
||||
}
|
||||
// Handle the last (not necessarily full) slot specially
|
||||
if <useSpill> {
|
||||
let data := sload(srcPtr)
|
||||
<#items>
|
||||
<encodeToMemoryFun>(<shiftRightFun>(data), pos)
|
||||
pos := add(pos, <elementEncodedSize>)
|
||||
if <inRange> {
|
||||
<encodeToMemoryFun>(<extractFromSlot>(data), pos)
|
||||
pos := add(pos, <elementEncodedSize>)
|
||||
itemCounter := add(itemCounter, 1)
|
||||
}
|
||||
</items>
|
||||
srcPtr := add(srcPtr, 1)
|
||||
}
|
||||
pos := add(originalPos, mul(length, <elementEncodedSize>))
|
||||
<assignEnd>
|
||||
}
|
||||
)"
|
||||
@@ -918,6 +975,15 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray(
|
||||
templ("lengthFun", m_utils.arrayLengthFunction(_from));
|
||||
templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options));
|
||||
templ("dataArea", m_utils.arrayDataAreaFunction(_from));
|
||||
// We skip the loop for arrays that fit a single slot.
|
||||
if (_from.isDynamicallySized() || _from.length() >= itemsPerSlot)
|
||||
templ("useLoop", "1");
|
||||
else
|
||||
templ("useLoop", "0");
|
||||
if (_from.isDynamicallySized() || spill != 0)
|
||||
templ("useSpill", "1");
|
||||
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());
|
||||
@@ -934,7 +1000,15 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray(
|
||||
templ("encodeToMemoryFun", encodeToMemoryFun);
|
||||
std::vector<std::map<std::string, std::string>> items(itemsPerSlot);
|
||||
for (size_t i = 0; i < itemsPerSlot; ++i)
|
||||
items[i]["shiftRightFun"] = m_utils.shiftRightFunction(i * storageBytes * 8);
|
||||
{
|
||||
if (_from.isDynamicallySized())
|
||||
items[i]["inRange"] = "lt(itemCounter, length)";
|
||||
else if (i < spill)
|
||||
items[i]["inRange"] = "1";
|
||||
else
|
||||
items[i]["inRange"] = "0";
|
||||
items[i]["extractFromSlot"] = extractFromStorageValue(*_from.baseType(), i * storageBytes, false);
|
||||
}
|
||||
templ("items", items);
|
||||
return templ.render();
|
||||
}
|
||||
@@ -1020,7 +1094,7 @@ string ABIFunctions::abiEncodingFunctionStruct(
|
||||
members.back()["preprocess"] = "slotValue := sload(add(value, " + toCompactHexWithPrefix(storageSlotOffset) + "))";
|
||||
previousSlotOffset = storageSlotOffset;
|
||||
}
|
||||
members.back()["retrieveValue"] = m_utils.shiftRightFunction(intraSlotOffset * 8) + "(slotValue)";
|
||||
members.back()["retrieveValue"] = extractFromStorageValue(*memberTypeFrom, intraSlotOffset, false) + "(slotValue)";
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1509,6 +1583,52 @@ string ABIFunctions::abiDecodingFunctionFunctionType(FunctionType const& _type,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
string ABIFunctions::readFromStorage(Type const& _type, size_t _offset, bool _splitFunctionTypes)
|
||||
{
|
||||
solUnimplementedAssert(!_splitFunctionTypes, "");
|
||||
string functionName =
|
||||
"read_from_storage_" +
|
||||
string(_splitFunctionTypes ? "split_" : "") +
|
||||
"offset_" +
|
||||
to_string(_offset) +
|
||||
_type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&] {
|
||||
solAssert(_type.sizeOnStack() == 1, "");
|
||||
return Whiskers(R"(
|
||||
function <functionName>(slot) -> value {
|
||||
value := <extract>(sload(slot))
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("extract", extractFromStorageValue(_type, _offset, false))
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
||||
string ABIFunctions::extractFromStorageValue(Type const& _type, size_t _offset, bool _splitFunctionTypes)
|
||||
{
|
||||
solUnimplementedAssert(!_splitFunctionTypes, "");
|
||||
|
||||
string functionName =
|
||||
"extract_from_storage_value_" +
|
||||
string(_splitFunctionTypes ? "split_" : "") +
|
||||
"offset_" +
|
||||
to_string(_offset) +
|
||||
_type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&] {
|
||||
return Whiskers(R"(
|
||||
function <functionName>(slot_value) -> value {
|
||||
value := <cleanupStorage>(<shr>(slot_value))
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("shr", m_utils.shiftRightFunction(_offset * 8))
|
||||
("cleanupStorage", cleanupFromStorageFunction(_type, false))
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
||||
string ABIFunctions::arrayStoreLengthForEncodingFunction(ArrayType const& _type, EncodingOptions const& _options)
|
||||
{
|
||||
string functionName = "array_storeLengthForEncoding_" + _type.identifier() + _options.toFunctionNameSuffix();
|
||||
|
||||
@@ -134,6 +134,15 @@ private:
|
||||
/// otherwise an assertion failure.
|
||||
std::string cleanupFunction(Type const& _type, bool _revertOnFailure = false);
|
||||
|
||||
/// Performs cleanup after reading from a potentially compressed storage slot.
|
||||
/// The function does not perform any validation, it just masks or sign-extends
|
||||
/// higher order bytes or left-aligns (in case of bytesNN).
|
||||
/// The storage cleanup expects the value to be right-aligned with potentially
|
||||
/// dirty higher order bytes.
|
||||
/// @param _splitFunctionTypes if false, returns the address and function signature in a
|
||||
/// single variable.
|
||||
std::string cleanupFromStorageFunction(Type const& _type, bool _splitFunctionTypes);
|
||||
|
||||
/// @returns the name of the function that converts a value of type @a _from
|
||||
/// to a value of type @a _to. The resulting vale is guaranteed to be in range
|
||||
/// (i.e. "clean"). Asserts on failure.
|
||||
@@ -233,6 +242,19 @@ private:
|
||||
/// Part of @a abiDecodingFunction for array types.
|
||||
std::string abiDecodingFunctionFunctionType(FunctionType const& _type, bool _fromMemory, bool _forUseOnStack);
|
||||
|
||||
/// @returns a function that reads a value type from storage.
|
||||
/// Performs bit mask/sign extend cleanup and appropriate left / right shift, but not validation.
|
||||
/// @param _splitFunctionTypes if false, returns the address and function signature in a
|
||||
/// single variable.
|
||||
std::string readFromStorage(Type const& _type, size_t _offset, bool _splitFunctionTypes);
|
||||
|
||||
/// @returns a function that extracts a value type from storage slot that has been
|
||||
/// retrieved already.
|
||||
/// Performs bit mask/sign extend cleanup and appropriate left / right shift, but not validation.
|
||||
/// @param _splitFunctionTypes if false, returns the address and function signature in a
|
||||
/// single variable.
|
||||
std::string extractFromStorageValue(Type const& _type, size_t _offset, bool _splitFunctionTypes);
|
||||
|
||||
/// @returns the name of a function used during encoding that stores the length
|
||||
/// if the array is dynamically sized (and the options do not request in-place encoding).
|
||||
/// It returns the new encoding position.
|
||||
|
||||
@@ -68,6 +68,7 @@ public:
|
||||
|
||||
std::string shiftLeftFunction(size_t _numBits);
|
||||
std::string shiftRightFunction(size_t _numBits);
|
||||
|
||||
/// @returns the name of a function that rounds its input to the next multiple
|
||||
/// of 32 or the input if it is a multiple of 32.
|
||||
std::string roundUpFunction();
|
||||
|
||||
Reference in New Issue
Block a user