Copy byte arrays from storage to storage.

This commit is contained in:
chriseth 2020-11-12 11:54:37 +01:00
parent 5431afcc8c
commit 62893aa1a1
7 changed files with 132 additions and 124 deletions

View File

@ -1571,12 +1571,13 @@ string YulUtilFunctions::copyByteArrayToStorageFunction(ArrayType const& _fromTy
); );
solAssert(_fromType.isByteArray(), ""); solAssert(_fromType.isByteArray(), "");
solAssert(_toType.isByteArray(), ""); solAssert(_toType.isByteArray(), "");
solUnimplementedAssert(!_fromType.dataStoredIn(DataLocation::Storage), "");
string functionName = "copy_byte_array_to_storage_from_" + _fromType.identifier() + "_to_" + _toType.identifier(); string functionName = "copy_byte_array_to_storage_from_" + _fromType.identifier() + "_to_" + _toType.identifier();
return m_functionCollector.createFunction(functionName, [&](){ return m_functionCollector.createFunction(functionName, [&](){
Whiskers templ(R"( Whiskers templ(R"(
function <functionName>(slot, src<?fromCalldata>, len</fromCalldata>) { function <functionName>(slot, src<?fromCalldata>, len</fromCalldata>) {
<?fromStorage> if eq(slot, src) { leave } </fromStorage>
let newLen := <arrayLength>(src<?fromCalldata>, len</fromCalldata>) let newLen := <arrayLength>(src<?fromCalldata>, len</fromCalldata>)
// Make sure array length is sane // Make sure array length is sane
if gt(newLen, 0xffffffffffffffff) { <panic>() } if gt(newLen, 0xffffffffffffffff) { <panic>() }
@ -1605,11 +1606,11 @@ string YulUtilFunctions::copyByteArrayToStorageFunction(ArrayType const& _fromTy
let dstPtr := dstDataArea let dstPtr := dstDataArea
let i := 0 let i := 0
for { } lt(i, loopEnd) { i := add(i, 32) } { for { } lt(i, loopEnd) { i := add(i, 32) } {
sstore(dstPtr, <readFromCalldataOrMemory>(add(src, i))) sstore(dstPtr, <read>(add(src, i)))
dstPtr := add(dstPtr, 1) dstPtr := add(dstPtr, 1)
} }
if lt(loopEnd, newLen) { if lt(loopEnd, newLen) {
let lastValue := <readFromCalldataOrMemory>(add(src, i)) let lastValue := <read>(add(src, i))
sstore(dstPtr, <maskBytes>(lastValue, and(newLen, 0x1f))) sstore(dstPtr, <maskBytes>(lastValue, and(newLen, 0x1f)))
} }
sstore(slot, add(mul(newLen, 2), 1)) sstore(slot, add(mul(newLen, 2), 1))
@ -1617,13 +1618,15 @@ string YulUtilFunctions::copyByteArrayToStorageFunction(ArrayType const& _fromTy
default { default {
let value := 0 let value := 0
if newLen { if newLen {
value := <readFromCalldataOrMemory>(src) value := <read>(src)
} }
sstore(slot, <byteArrayCombineShort>(value, newLen)) sstore(slot, <byteArrayCombineShort>(value, newLen))
} }
} }
)"); )");
templ("functionName", functionName); templ("functionName", functionName);
bool fromStorage = _fromType.dataStoredIn(DataLocation::Storage);
templ("fromStorage", fromStorage);
bool fromCalldata = _fromType.dataStoredIn(DataLocation::CallData); bool fromCalldata = _fromType.dataStoredIn(DataLocation::CallData);
templ("fromMemory", _fromType.dataStoredIn(DataLocation::Memory)); templ("fromMemory", _fromType.dataStoredIn(DataLocation::Memory));
templ("fromCalldata", fromCalldata); templ("fromCalldata", fromCalldata);
@ -1632,7 +1635,7 @@ string YulUtilFunctions::copyByteArrayToStorageFunction(ArrayType const& _fromTy
templ("byteArrayLength", extractByteArrayLengthFunction()); templ("byteArrayLength", extractByteArrayLengthFunction());
templ("dstDataLocation", arrayDataAreaFunction(_toType)); templ("dstDataLocation", arrayDataAreaFunction(_toType));
templ("clearStorageRange", clearStorageRangeFunction(*_toType.baseType())); templ("clearStorageRange", clearStorageRangeFunction(*_toType.baseType()));
templ("readFromCalldataOrMemory", readFromMemoryOrCalldata(*TypeProvider::uint256(), fromCalldata)); templ("read", fromStorage ? "sload" : fromCalldata ? "calldataload" : "mload");
templ("maskBytes", maskBytesFunctionDynamic()); templ("maskBytes", maskBytesFunctionDynamic());
templ("byteArrayCombineShort", shortByteArrayEncodeUsedAreaSetLengthFunction()); templ("byteArrayCombineShort", shortByteArrayEncodeUsedAreaSetLengthFunction());
@ -2200,132 +2203,130 @@ string YulUtilFunctions::updateStorageValueFunction(
("prepare", prepareStoreFunction(_toType)) ("prepare", prepareStoreFunction(_toType))
.render(); .render();
} }
auto const* toReferenceType = dynamic_cast<ReferenceType const*>(&_toType);
auto const* fromReferenceType = dynamic_cast<ReferenceType const*>(&_fromType);
solAssert(fromReferenceType && toReferenceType, "");
solAssert(*toReferenceType->copyForLocation(
fromReferenceType->location(),
fromReferenceType->isPointer()
).get() == *fromReferenceType, "");
solAssert(toReferenceType->category() == fromReferenceType->category(), "");
if (_toType.category() == Type::Category::Array)
{
solAssert(_offset.value_or(0) == 0, "");
Whiskers templ(R"(
function <functionName>(slot, <value>) {
<copyArrayToStorage>(slot, <value>)
}
)");
templ("functionName", functionName);
templ("value", suffixedVariableNameList("value_", 0, _fromType.sizeOnStack()));
templ("copyArrayToStorage", copyArrayToStorageFunction(
dynamic_cast<ArrayType const&>(_fromType),
dynamic_cast<ArrayType const&>(_toType)
));
return templ.render();
}
else else
{ {
auto const* toReferenceType = dynamic_cast<ReferenceType const*>(&_toType); solAssert(_toType.category() == Type::Category::Struct, "");
auto const* fromReferenceType = dynamic_cast<ReferenceType const*>(&_fromType);
solAssert(fromReferenceType && toReferenceType, "");
solAssert(*toReferenceType->copyForLocation(
fromReferenceType->location(),
fromReferenceType->isPointer()
).get() == *fromReferenceType, "");
solUnimplementedAssert( solUnimplementedAssert(
fromReferenceType->location() != DataLocation::Storage, fromReferenceType->location() != DataLocation::Storage,
"Copying from storage to storage is not yet implemented." "Copying from storage to storage is not yet implemented."
); );
solAssert(toReferenceType->category() == fromReferenceType->category(), ""); auto const& fromStructType = dynamic_cast<StructType const&>(_fromType);
auto const& toStructType = dynamic_cast<StructType const&>(_toType);
solAssert(fromStructType.structDefinition() == toStructType.structDefinition(), "");
solAssert(_offset.value_or(0) == 0, "");
if (_toType.category() == Type::Category::Array) Whiskers templ(R"(
{ function <functionName>(slot, value) {
solAssert(_offset.value_or(0) == 0, ""); <#member>
Whiskers templ(R"(
function <functionName>(slot, <value>) {
<copyArrayToStorage>(slot, <value>)
}
)");
templ("functionName", functionName);
templ("value", suffixedVariableNameList("value_", 0, _fromType.sizeOnStack()));
templ("copyArrayToStorage", copyArrayToStorageFunction(
dynamic_cast<ArrayType const&>(_fromType),
dynamic_cast<ArrayType const&>(_toType)
));
return templ.render();
}
else if (_toType.category() == Type::Category::Struct)
{
auto const& fromStructType = dynamic_cast<StructType const&>(_fromType);
auto const& toStructType = dynamic_cast<StructType const&>(_toType);
solAssert(fromStructType.structDefinition() == toStructType.structDefinition(), "");
solAssert(_offset.value_or(0) == 0, "");
Whiskers templ(R"(
function <functionName>(slot, value) {
<#member>
{
<updateMemberCall>
}
</member>
}
)");
templ("functionName", functionName);
MemberList::MemberMap structMembers = fromStructType.nativeMembers(nullptr);
MemberList::MemberMap toStructMembers = toStructType.nativeMembers(nullptr);
vector<map<string, string>> memberParams(structMembers.size());
for (size_t i = 0; i < structMembers.size(); ++i)
{
solAssert(structMembers[i].type->memoryHeadSize() == 32, "");
bool fromCalldata = fromStructType.location() == DataLocation::CallData;
auto const& [slotDiff, offset] = toStructType.storageOffsetsOfMember(structMembers[i].name);
Whiskers t(R"(
let memberSlot := add(slot, <memberStorageSlotDiff>)
<?fromCalldata>
<?dynamicallyEncodedMember>
let <memberCalldataOffset> := <accessCalldataTail>(value, add(value, <memberOffset>))
<!dynamicallyEncodedMember>
let <memberCalldataOffset> := add(value, <memberOffset>)
</dynamicallyEncodedMember>
<?isValueType>
let <memberValues> := <loadFromMemoryOrCalldata>(<memberCalldataOffset>)
<updateMember>(memberSlot, <memberStorageOffset>, <memberValues>)
<!isValueType>
<updateMember>(memberSlot, <memberCalldataOffset>)
</isValueType>
<!fromCalldata>
let memberMemoryOffset := add(value, <memberOffset>)
let <memberValues> := <loadFromMemoryOrCalldata>(memberMemoryOffset)
<updateMember>(memberSlot, <?hasOffset><memberStorageOffset>,</hasOffset> <memberValues>)
</fromCalldata>
)");
t("fromCalldata", fromCalldata);
if (fromCalldata)
{ {
t("memberCalldataOffset", suffixedVariableNameList( <updateMemberCall>
"memberCalldataOffset_",
0,
structMembers[i].type->stackItems().size()
));
t("dynamicallyEncodedMember", structMembers[i].type->isDynamicallyEncoded());
if (structMembers[i].type->isDynamicallyEncoded())
t("accessCalldataTail", accessCalldataTailFunction(*structMembers[i].type));
} }
t("isValueType", structMembers[i].type->isValueType()); </member>
t("memberValues", suffixedVariableNameList( }
"memberValue_", )");
templ("functionName", functionName);
MemberList::MemberMap structMembers = fromStructType.nativeMembers(nullptr);
MemberList::MemberMap toStructMembers = toStructType.nativeMembers(nullptr);
vector<map<string, string>> memberParams(structMembers.size());
for (size_t i = 0; i < structMembers.size(); ++i)
{
solAssert(structMembers[i].type->memoryHeadSize() == 32, "");
bool fromCalldata = fromStructType.location() == DataLocation::CallData;
auto const& [slotDiff, offset] = toStructType.storageOffsetsOfMember(structMembers[i].name);
Whiskers t(R"(
let memberSlot := add(slot, <memberStorageSlotDiff>)
<?fromCalldata>
<?dynamicallyEncodedMember>
let <memberCalldataOffset> := <accessCalldataTail>(value, add(value, <memberOffset>))
<!dynamicallyEncodedMember>
let <memberCalldataOffset> := add(value, <memberOffset>)
</dynamicallyEncodedMember>
<?isValueType>
let <memberValues> := <loadFromMemoryOrCalldata>(<memberCalldataOffset>)
<updateMember>(memberSlot, <memberStorageOffset>, <memberValues>)
<!isValueType>
<updateMember>(memberSlot, <memberCalldataOffset>)
</isValueType>
<!fromCalldata>
let memberMemoryOffset := add(value, <memberOffset>)
let <memberValues> := <loadFromMemoryOrCalldata>(memberMemoryOffset)
<updateMember>(memberSlot, <?hasOffset><memberStorageOffset>,</hasOffset> <memberValues>)
</fromCalldata>
)");
t("fromCalldata", fromCalldata);
if (fromCalldata)
{
t("memberCalldataOffset", suffixedVariableNameList(
"memberCalldataOffset_",
0, 0,
structMembers[i].type->stackItems().size() structMembers[i].type->stackItems().size()
)); ));
t("hasOffset", structMembers[i].type->isValueType()); t("dynamicallyEncodedMember", structMembers[i].type->isDynamicallyEncoded());
t( if (structMembers[i].type->isDynamicallyEncoded())
"updateMember", t("accessCalldataTail", accessCalldataTailFunction(*structMembers[i].type));
structMembers[i].type->isValueType() ?
updateStorageValueFunction(*structMembers[i].type, *toStructMembers[i].type) :
updateStorageValueFunction(*structMembers[i].type, *toStructMembers[i].type, offset)
);
t("memberStorageSlotDiff", slotDiff.str());
t("memberStorageOffset", to_string(offset));
t(
"memberOffset",
fromCalldata ?
to_string(fromStructType.calldataOffsetOfMember(structMembers[i].name)) :
fromStructType.memoryOffsetOfMember(structMembers[i].name).str()
);
if (!fromCalldata || structMembers[i].type->isValueType())
t("loadFromMemoryOrCalldata", readFromMemoryOrCalldata(*structMembers[i].type, fromCalldata));
memberParams[i]["updateMemberCall"] = t.render();
} }
templ("member", memberParams); t("isValueType", structMembers[i].type->isValueType());
t("memberValues", suffixedVariableNameList(
return templ.render(); "memberValue_",
0,
structMembers[i].type->stackItems().size()
));
t("hasOffset", structMembers[i].type->isValueType());
t(
"updateMember",
structMembers[i].type->isValueType() ?
updateStorageValueFunction(*structMembers[i].type, *toStructMembers[i].type) :
updateStorageValueFunction(*structMembers[i].type, *toStructMembers[i].type, offset)
);
t("memberStorageSlotDiff", slotDiff.str());
t("memberStorageOffset", to_string(offset));
t(
"memberOffset",
fromCalldata ?
to_string(fromStructType.calldataOffsetOfMember(structMembers[i].name)) :
fromStructType.memoryOffsetOfMember(structMembers[i].name).str()
);
if (!fromCalldata || structMembers[i].type->isValueType())
t("loadFromMemoryOrCalldata", readFromMemoryOrCalldata(*structMembers[i].type, fromCalldata));
memberParams[i]["updateMemberCall"] = t.render();
} }
else templ("member", memberParams);
solAssert(false, "Invalid non-value type for assignment.");
return templ.render();
} }
}); });
} }

View File

@ -128,7 +128,7 @@ function test_solc_behaviour()
sed -i.bak -e '/^Warning (3805): This is a pre-release compiler version, please do not use it in production./d' "$stderr_path" sed -i.bak -e '/^Warning (3805): This is a pre-release compiler version, please do not use it in production./d' "$stderr_path"
sed -i.bak -e 's/\(^[ ]*auxdata: \)0x[0-9a-f]*$/\1<AUXDATA REMOVED>/' "$stdout_path" sed -i.bak -e 's/\(^[ ]*auxdata: \)0x[0-9a-f]*$/\1<AUXDATA REMOVED>/' "$stdout_path"
sed -i.bak -e 's/ Consider adding "pragma .*$//' "$stderr_path" sed -i.bak -e 's/ Consider adding "pragma .*$//' "$stderr_path"
sed -i.bak -e 's/\(Unimplemented feature error: .* in \).*$/\1<FILENAME REMOVED>/' "$stderr_path" sed -i.bak -e 's/\(Unimplemented feature error.* in \).*$/\1<FILENAME REMOVED>/' "$stderr_path"
sed -i.bak -e 's/"version": "[^"]*"/"version": "<VERSION REMOVED>"/' "$stdout_path" sed -i.bak -e 's/"version": "[^"]*"/"version": "<VERSION REMOVED>"/' "$stdout_path"
# Remove bytecode (but not linker references). Since non-JSON output is unstructured, # Remove bytecode (but not linker references). Since non-JSON output is unstructured,

View File

@ -1,5 +1,5 @@
Error (1834): Unimplemented feature error: Copying from storage to storage is not yet implemented. in <FILENAME REMOVED> Error (1834): Unimplemented feature error in <FILENAME REMOVED>
--> yul_unimplemented/input.sol:7:9: --> yul_unimplemented/input.sol:8:9:
| |
7 | a = b; 8 | x.f();
| ^^^^^ | ^^^^^

View File

@ -1,9 +1,10 @@
// SPDX-License-Identifier: GPL-3.0 // SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.0; pragma solidity >=0.0;
library L { function f(uint) public {} }
contract test { contract test {
bytes a; using L for uint;
bytes b;
function f() public { function f() public {
a = b; uint x;
x.f();
} }
} }

View File

@ -3,6 +3,8 @@ contract c {
function copy(uint from, uint to) public returns (bool) { data[to] = data[from]; return true; } function copy(uint from, uint to) public returns (bool) { data[to] = data[from]; return true; }
mapping(uint => bytes) data; mapping(uint => bytes) data;
} }
// ====
// compileViaYul: also
// ---- // ----
// set(uint256): 1, 2 -> true // set(uint256): 1, 2 -> true
// set(uint256): 2, 2, 3, 4, 5 -> true // set(uint256): 2, 2, 3, 4, 5 -> true

View File

@ -5,6 +5,8 @@ contract c {
bytes data1; bytes data1;
bytes data2; bytes data2;
} }
// ====
// compileViaYul: also
// ---- // ----
// set(): 1, 2, 3, 4, 5 -> true // set(): 1, 2, 3, 4, 5 -> true
// storage: nonempty // storage: nonempty

View File

@ -12,6 +12,8 @@ contract Test {
return bytes(s).length; return bytes(s).length;
} }
} }
// ====
// compileViaYul: also
// ---- // ----
// f(string,uint256): 0x40, 0x02, 0x06, "abcdef" -> "c" // f(string,uint256): 0x40, 0x02, 0x06, "abcdef" -> "c"
// l() -> 0x06 // l() -> 0x06