[Sol->Yul] Implementing resizing of dynamic byte arrays

Co-authored-by: chriseth <chris@ethereum.org>
This commit is contained in:
Djordje Mijovic 2020-10-06 16:00:32 +02:00
parent 9aafb62e52
commit 4a66723ff9
4 changed files with 217 additions and 15 deletions

View File

@ -959,9 +959,11 @@ std::string YulUtilFunctions::resizeDynamicArrayFunction(ArrayType const& _type)
{
solAssert(_type.location() == DataLocation::Storage, "");
solAssert(_type.isDynamicallySized(), "");
solUnimplementedAssert(!_type.isByteArray(), "Byte Arrays not yet implemented!");
solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "...");
if (_type.isByteArray())
return resizeDynamicByteArrayFunction(_type);
string functionName = "resize_array_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
@ -1006,6 +1008,151 @@ std::string YulUtilFunctions::resizeDynamicArrayFunction(ArrayType const& _type)
});
}
string YulUtilFunctions::resizeDynamicByteArrayFunction(ArrayType const& _type)
{
string functionName = "resize_array_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(array, newLen) {
if gt(newLen, <maxArrayLength>) {
<panic>()
}
let data := sload(array)
let oldLen := <extractLength>(data)
if gt(newLen, oldLen) {
<increaseSize>(array, data, oldLen, newLen)
}
if lt(newLen, oldLen) {
<decreaseSize>(array, data, oldLen, newLen)
}
})")
("functionName", functionName)
("panic", panicFunction())
("extractLength", extractByteArrayLengthFunction())
("maxArrayLength", (u256(1) << 64).str())
("decreaseSize", decreaseByteArraySizeFunction(_type))
("increaseSize", increaseByteArraySizeFunction(_type))
.render();
});
}
string YulUtilFunctions::decreaseByteArraySizeFunction(ArrayType const& _type)
{
string functionName = "byte_array_decrease_size_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(array, data, oldLen, newLen) {
switch lt(newLen, 32)
case 0 {
let arrayDataStart := <dataPosition>(array)
let deleteStart := add(arrayDataStart, div(add(newLen, 31), 32))
// we have to partially clear last slot that is still used
let offset := and(newLen, 0x1f)
if offset { <partialClearStorageSlot>(sub(deleteStart, 1), offset) }
<clearStorageRange>(deleteStart, add(arrayDataStart, div(add(oldLen, 31), 32)))
sstore(array, or(mul(2, newLen), 1))
}
default {
switch gt(oldLen, 31)
case 1 {
let arrayDataStart := <dataPosition>(array)
// clear whole old array, as we are transforming to short bytes array
<clearStorageRange>(add(arrayDataStart, 1), add(arrayDataStart, div(add(oldLen, 31), 32)))
<transitLongToShort>(array, newLen)
}
default {
sstore(array, <encodeUsedSetLen>(data, newLen))
}
}
})")
("functionName", functionName)
("dataPosition", arrayDataAreaFunction(_type))
("partialClearStorageSlot", partialClearStorageSlotFunction())
("clearStorageRange", clearStorageRangeFunction(*_type.baseType()))
("transitLongToShort", byteArrayTransitLongToShortFunction(_type))
("encodeUsedSetLen", shortByteArrayEncodeUsedAreaSetLengthFunction())
.render();
});
}
string YulUtilFunctions::increaseByteArraySizeFunction(ArrayType const& _type)
{
string functionName = "byte_array_increase_size_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(array, data, oldLen, newLen) {
switch lt(oldLen, 32)
case 0 {
// in this case array stays unpacked, so we just set new length
sstore(array, add(mul(2, newLen), 1))
}
default {
switch lt(newLen, 32)
case 0 {
// we need to copy elements to data area as we changed array from packed to unpacked
data := and(not(0xff), data)
sstore(<dataPosition>(array), data)
sstore(array, add(mul(2, newLen), 1))
}
default {
// here array stays packed, we just need to increase length
sstore(array, <encodeUsedSetLen>(data, newLen))
}
}
})")
("functionName", functionName)
("dataPosition", arrayDataAreaFunction(_type))
("encodeUsedSetLen", shortByteArrayEncodeUsedAreaSetLengthFunction())
.render();
});
}
string YulUtilFunctions::byteArrayTransitLongToShortFunction(ArrayType const& _type)
{
string functionName = "transit_byte_array_long_to_short_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(array, len) {
// we need to copy elements from old array to new
// we want to copy only elements that are part of the array after resizing
let dataPos := <dataPosition>(array)
let data := <extractUsedApplyLen>(sload(dataPos), len)
sstore(array, data)
sstore(dataPos, 0)
})")
("functionName", functionName)
("dataPosition", arrayDataAreaFunction(_type))
("extractUsedApplyLen", shortByteArrayEncodeUsedAreaSetLengthFunction())
("shl", shiftLeftFunctionDynamic())
("ones", formatNumber((bigint(1) << 256) - 1))
.render();
});
}
string YulUtilFunctions::shortByteArrayEncodeUsedAreaSetLengthFunction()
{
string functionName = "extract_used_part_and_set_length_of_short_byte_array";
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(data, len) -> used {
// we want to save only elements that are part of the array after resizing
// others should be set to zero
let mask := <shl>(mul(8, sub(32, len)), <ones>)
used := or(and(data, mask), mul(2, len))
})")
("functionName", functionName)
("shl", shiftLeftFunctionDynamic())
("ones", formatNumber((bigint(1) << 256) - 1))
.render();
});
}
string YulUtilFunctions::storageArrayPopFunction(ArrayType const& _type)
{
solAssert(_type.location() == DataLocation::Storage, "");
@ -1048,36 +1195,30 @@ string YulUtilFunctions::storageByteArrayPopFunction(ArrayType const& _type)
let oldLen := <extractByteArrayLength>(data)
if iszero(oldLen) { <panic>() }
switch eq(oldLen, 32)
case 1 {
switch oldLen
case 32 {
// Here we have a special case where array transitions to shorter than 32
// So we need to copy data
let copyFromSlot := <dataAreaFunction>(array)
data := sload(copyFromSlot)
sstore(copyFromSlot, 0)
// New length is 31, encoded to 31 * 2 = 62
data := or(and(data, not(0xff)), 62)
<transitLongToShort>(array, 31)
}
default {
data := sub(data, 2)
let newLen := sub(oldLen, 1)
switch lt(oldLen, 32)
case 1 {
// set last element to zero
let mask := not(<shl>(mul(8, sub(31, newLen)), 0xff))
data := and(data, mask)
sstore(array, <encodeUsedSetLen>(data, newLen))
}
default {
let slot, offset := <indexAccess>(array, newLen)
<setToZero>(slot, offset)
sstore(array, sub(data, 2))
}
}
sstore(array, data)
})")
("functionName", functionName)
("panic", panicFunction())
("extractByteArrayLength", extractByteArrayLengthFunction())
("dataAreaFunction", arrayDataAreaFunction(_type))
("transitLongToShort", byteArrayTransitLongToShortFunction(_type))
("encodeUsedSetLen", shortByteArrayEncodeUsedAreaSetLengthFunction())
("indexAccess", storageArrayIndexAccessFunction(_type))
("setToZero", storageSetToZeroFunction(*_type.baseType()))
("shl", shiftLeftFunctionDynamic())

View File

@ -446,6 +446,29 @@ private:
/// signature: (slot) ->
std::string clearStorageStructFunction(StructType const& _type);
/// @returns the name of a function that resizes a storage byte array
/// signature: (array, newLen)
std::string resizeDynamicByteArrayFunction(ArrayType const& _type);
/// @returns the name of a function that increases size of byte array
/// when we resize byte array frextractUsedSetLenom < 32 elements to >= 32 elements or we push to byte array of size 31 copying of data will occur
/// signature: (array, data, oldLen, newLen)
std::string increaseByteArraySizeFunction(ArrayType const& _type);
/// @returns the name of a function that decreases size of byte array
/// when we resize byte array from >= 32 elements to < 32 elements or we pop from byte array of size 32 copying of data will occur
/// signature: (array, data, oldLen, newLen)
std::string decreaseByteArraySizeFunction(ArrayType const& _type);
/// @returns the name of a function that sets size of short byte array while copying data
/// should be called when we resize from long byte array (more than 32 elements) to short byte array
/// signature: (array, data, len)
std::string byteArrayTransitLongToShortFunction(ArrayType const& _type);
/// @returns the name of a function that extracts only used part of slot that represents short byte array
/// signature: (data, len) -> data
std::string shortByteArrayEncodeUsedAreaSetLengthFunction();
langutil::EVMVersion m_evmVersion;
RevertStrings m_revertStrings;
MultiUseYulFunctionCollector& m_functionCollector;

View File

@ -32,6 +32,7 @@ contract c {
data.push(bytes1(i));
}
data.pop();
data.pop();
assembly {
r := sload(data.slot)
}
@ -42,4 +43,4 @@ contract c {
// ----
// test_short() -> 1780731860627700044960722568376587075150542249149356309979516913770823710
// test_long() -> 67
// test_pop() -> 1780731860627700044960722568376592200742329637303199754547598369979440702
// test_pop() -> 1780731860627700044960722568376592200742329637303199754547598369979433020

View File

@ -0,0 +1,37 @@
contract C {
bytes data;
function f() public returns (uint ret) {
data.push("a");
data.push("b");
delete data;
assembly {
ret := sload(data.slot)
}
}
function g() public returns (uint ret) {
assembly {
sstore(data.slot, 67)
}
data.push("a");
data.push("b");
assert(data.length == 35);
delete data;
assert(data.length == 0);
uint size = 999;
assembly {
size := sload(data.slot)
mstore(0, data.slot)
ret := sload(keccak256(0, 32))
}
assert(size == 0);
}
}
// ====
// compileViaYul: also
// ----
// f() -> 0
// g() -> 0