mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge remote-tracking branch 'origin/develop' into breaking
This commit is contained in:
@@ -36,21 +36,52 @@ using namespace solidity::frontend;
|
||||
namespace
|
||||
{
|
||||
|
||||
void copyMissingTags(StructurallyDocumentedAnnotation& _target, set<CallableDeclaration const*> const& _baseFunctions)
|
||||
void copyMissingTags(set<CallableDeclaration const*> const& _baseFunctions, StructurallyDocumentedAnnotation& _target, CallableDeclaration const* _declaration = nullptr)
|
||||
{
|
||||
// Only copy if there is exactly one direct base function.
|
||||
if (_baseFunctions.size() != 1)
|
||||
return;
|
||||
|
||||
auto& sourceDoc = dynamic_cast<StructurallyDocumentedAnnotation const&>((*_baseFunctions.begin())->annotation());
|
||||
|
||||
set<string> existingTags;
|
||||
for (auto it = sourceDoc.docTags.begin(); it != sourceDoc.docTags.end();)
|
||||
{
|
||||
string const& tag = it->first;
|
||||
// Don't copy tag "inheritdoc" or already existing tags
|
||||
if (tag == "inheritdoc" || _target.docTags.count(tag))
|
||||
{
|
||||
it++;
|
||||
continue;
|
||||
}
|
||||
|
||||
for (auto const& iterator: _target.docTags)
|
||||
existingTags.insert(iterator.first);
|
||||
size_t n = 0;
|
||||
// Iterate over all values of the current tag (it's a multimap)
|
||||
for (auto next = sourceDoc.docTags.upper_bound(tag); it != next; it++, n++)
|
||||
{
|
||||
DocTag content = it->second;
|
||||
|
||||
// Update the parameter name for @return tags
|
||||
if (_declaration && tag == "return")
|
||||
{
|
||||
size_t docParaNameEndPos = content.content.find_first_of(" \t");
|
||||
string const docParameterName = content.content.substr(0, docParaNameEndPos);
|
||||
|
||||
if (docParameterName != _declaration->returnParameters().at(n)->name())
|
||||
{
|
||||
bool baseHasNoName = (*_baseFunctions.begin())->returnParameters().at(n)->name().empty();
|
||||
string paramName = _declaration->returnParameters().at(n)->name();
|
||||
content.content =
|
||||
(paramName.empty() ? "" : std::move(paramName) + " ") + (
|
||||
string::npos == docParaNameEndPos || baseHasNoName ?
|
||||
content.content :
|
||||
content.content.substr(docParaNameEndPos + 1)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto const& [tag, content]: sourceDoc.docTags)
|
||||
if (tag != "inheritdoc" && !existingTags.count(tag))
|
||||
_target.docTags.emplace(tag, content);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CallableDeclaration const* findBaseCallable(set<CallableDeclaration const*> const& _baseFunctions, int64_t _contractId)
|
||||
@@ -91,9 +122,9 @@ bool DocStringAnalyser::visit(VariableDeclaration const& _variable)
|
||||
return false;
|
||||
|
||||
if (CallableDeclaration const* baseFunction = resolveInheritDoc(_variable.annotation().baseFunctions, _variable, _variable.annotation()))
|
||||
copyMissingTags(_variable.annotation(), {baseFunction});
|
||||
copyMissingTags({baseFunction}, _variable.annotation());
|
||||
else if (_variable.annotation().docTags.empty())
|
||||
copyMissingTags(_variable.annotation(), _variable.annotation().baseFunctions);
|
||||
copyMissingTags(_variable.annotation().baseFunctions, _variable.annotation());
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -119,13 +150,13 @@ void DocStringAnalyser::handleCallable(
|
||||
)
|
||||
{
|
||||
if (CallableDeclaration const* baseFunction = resolveInheritDoc(_callable.annotation().baseFunctions, _node, _annotation))
|
||||
copyMissingTags(_annotation, {baseFunction});
|
||||
copyMissingTags({baseFunction}, _annotation, &_callable);
|
||||
else if (
|
||||
_annotation.docTags.empty() &&
|
||||
_callable.annotation().baseFunctions.size() == 1 &&
|
||||
parameterNamesEqual(_callable, **_callable.annotation().baseFunctions.begin())
|
||||
)
|
||||
copyMissingTags(_annotation, _callable.annotation().baseFunctions);
|
||||
copyMissingTags(_callable.annotation().baseFunctions, _annotation, &_callable);
|
||||
}
|
||||
|
||||
CallableDeclaration const* DocStringAnalyser::resolveInheritDoc(
|
||||
|
||||
@@ -449,6 +449,36 @@ string YulUtilFunctions::maskBytesFunctionDynamic()
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::maskLowerOrderBytesFunction(size_t _bytes)
|
||||
{
|
||||
string functionName = "mask_lower_order_bytes_" + to_string(_bytes);
|
||||
solAssert(_bytes <= 32, "");
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
return Whiskers(R"(
|
||||
function <functionName>(data) -> result {
|
||||
result := and(data, <mask>)
|
||||
})")
|
||||
("functionName", functionName)
|
||||
("mask", formatNumber((~u256(0)) >> (256 - 8 * _bytes)))
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::maskLowerOrderBytesFunctionDynamic()
|
||||
{
|
||||
string functionName = "mask_lower_order_bytes_dynamic";
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
return Whiskers(R"(
|
||||
function <functionName>(data, bytes) -> result {
|
||||
let mask := not(<shl>(mul(8, bytes), not(0)))
|
||||
result := and(data, mask)
|
||||
})")
|
||||
("functionName", functionName)
|
||||
("shl", shiftLeftFunctionDynamic())
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::roundUpFunction()
|
||||
{
|
||||
string functionName = "round_up_to_mul_of_32";
|
||||
@@ -1342,7 +1372,6 @@ string YulUtilFunctions::storageArrayPushFunction(ArrayType const& _type)
|
||||
{
|
||||
solAssert(_type.location() == DataLocation::Storage, "");
|
||||
solAssert(_type.isDynamicallySized(), "");
|
||||
solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "Base type is not yet implemented.");
|
||||
|
||||
string functionName = "array_push_" + _type.identifier();
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
@@ -1568,23 +1597,20 @@ string YulUtilFunctions::copyArrayToStorageFunction(ArrayType const& _fromType,
|
||||
);
|
||||
if (_fromType.isByteArray())
|
||||
return copyByteArrayToStorageFunction(_fromType, _toType);
|
||||
solUnimplementedAssert(!_fromType.dataStoredIn(DataLocation::Storage), "");
|
||||
if (_fromType.dataStoredIn(DataLocation::Storage) && _toType.baseType()->isValueType())
|
||||
return copyValueArrayStorageToStorageFunction(_fromType, _toType);
|
||||
|
||||
string functionName = "copy_array_to_storage_from_" + _fromType.identifier() + "_to_" + _toType.identifier();
|
||||
return m_functionCollector.createFunction(functionName, [&](){
|
||||
Whiskers templ(R"(
|
||||
function <functionName>(slot, value<?isFromDynamicCalldata>, len</isFromDynamicCalldata>) {
|
||||
<?fromStorage> if eq(slot, value) { leave } </fromStorage>
|
||||
let length := <arrayLength>(value<?isFromDynamicCalldata>, len</isFromDynamicCalldata>)
|
||||
<?isToDynamic>
|
||||
<resizeArray>(slot, length)
|
||||
</isToDynamic>
|
||||
|
||||
let srcPtr :=
|
||||
<?isFromMemoryDynamic>
|
||||
add(value, 0x20)
|
||||
<!isFromMemoryDynamic>
|
||||
value
|
||||
</isFromMemoryDynamic>
|
||||
let srcPtr := <srcDataLocation>(value)
|
||||
|
||||
let elementSlot := <dstDataLocation>(slot)
|
||||
let elementOffset := 0
|
||||
@@ -1607,9 +1633,13 @@ string YulUtilFunctions::copyArrayToStorageFunction(ArrayType const& _fromType,
|
||||
let <elementValues> := <readFromCalldataOrMemory>(srcPtr)
|
||||
</fromMemory>
|
||||
|
||||
<updateStorageValue>(elementSlot<?isValueType>, elementOffset</isValueType>, <elementValues>)
|
||||
<?fromStorage>
|
||||
let <elementValues> := srcPtr
|
||||
</fromStorage>
|
||||
|
||||
srcPtr := add(srcPtr, <stride>)
|
||||
<updateStorageValue>(elementSlot, elementOffset, <elementValues>)
|
||||
|
||||
srcPtr := add(srcPtr, <srcStride>)
|
||||
|
||||
<?multipleItemsPerSlot>
|
||||
elementOffset := add(elementOffset, <storageStride>)
|
||||
@@ -1619,18 +1649,21 @@ string YulUtilFunctions::copyArrayToStorageFunction(ArrayType const& _fromType,
|
||||
}
|
||||
<!multipleItemsPerSlot>
|
||||
elementSlot := add(elementSlot, <storageSize>)
|
||||
elementOffset := 0
|
||||
</multipleItemsPerSlot>
|
||||
}
|
||||
}
|
||||
)");
|
||||
if (_fromType.dataStoredIn(DataLocation::Storage))
|
||||
solAssert(!_fromType.isValueType(), "");
|
||||
templ("functionName", functionName);
|
||||
bool fromCalldata = _fromType.dataStoredIn(DataLocation::CallData);
|
||||
templ("isFromDynamicCalldata", _fromType.isDynamicallySized() && fromCalldata);
|
||||
templ("fromMemory", _fromType.dataStoredIn(DataLocation::Memory));
|
||||
templ("fromStorage", _fromType.dataStoredIn(DataLocation::Storage));
|
||||
bool fromMemory = _fromType.dataStoredIn(DataLocation::Memory);
|
||||
templ("fromMemory", fromMemory);
|
||||
templ("fromCalldata", fromCalldata);
|
||||
templ("isToDynamic", _toType.isDynamicallySized());
|
||||
templ("isFromMemoryDynamic", _fromType.isDynamicallySized() && _fromType.dataStoredIn(DataLocation::Memory));
|
||||
templ("srcDataLocation", arrayDataAreaFunction(_fromType));
|
||||
if (fromCalldata)
|
||||
{
|
||||
templ("dynamicallySizedBase", _fromType.baseType()->isDynamicallySized());
|
||||
@@ -1643,7 +1676,7 @@ string YulUtilFunctions::copyArrayToStorageFunction(ArrayType const& _fromType,
|
||||
templ("arrayLength",arrayLengthFunction(_fromType));
|
||||
templ("isValueType", _fromType.baseType()->isValueType());
|
||||
templ("dstDataLocation", arrayDataAreaFunction(_toType));
|
||||
if (!fromCalldata || _fromType.baseType()->isValueType())
|
||||
if (fromMemory || (fromCalldata && _fromType.baseType()->isValueType()))
|
||||
templ("readFromCalldataOrMemory", readFromMemoryOrCalldata(*_fromType.baseType(), fromCalldata));
|
||||
templ("elementValues", suffixedVariableNameList(
|
||||
"elementValue_",
|
||||
@@ -1651,7 +1684,13 @@ string YulUtilFunctions::copyArrayToStorageFunction(ArrayType const& _fromType,
|
||||
_fromType.baseType()->stackItems().size()
|
||||
));
|
||||
templ("updateStorageValue", updateStorageValueFunction(*_fromType.baseType(), *_toType.baseType()));
|
||||
templ("stride", to_string(fromCalldata ? _fromType.calldataStride() : _fromType.memoryStride()));
|
||||
templ("srcStride",
|
||||
fromCalldata ?
|
||||
to_string(_fromType.calldataStride()) :
|
||||
fromMemory ?
|
||||
to_string(_fromType.memoryStride()) :
|
||||
formatNumber(_fromType.baseType()->storageSize())
|
||||
);
|
||||
templ("multipleItemsPerSlot", _toType.storageStride() <= 16);
|
||||
templ("storageStride", to_string(_toType.storageStride()));
|
||||
templ("storageSize", _toType.baseType()->storageSize().str());
|
||||
@@ -1669,12 +1708,13 @@ string YulUtilFunctions::copyByteArrayToStorageFunction(ArrayType const& _fromTy
|
||||
);
|
||||
solAssert(_fromType.isByteArray(), "");
|
||||
solAssert(_toType.isByteArray(), "");
|
||||
solUnimplementedAssert(!_fromType.dataStoredIn(DataLocation::Storage), "");
|
||||
|
||||
string functionName = "copy_byte_array_to_storage_from_" + _fromType.identifier() + "_to_" + _toType.identifier();
|
||||
return m_functionCollector.createFunction(functionName, [&](){
|
||||
Whiskers templ(R"(
|
||||
function <functionName>(slot, src<?fromCalldata>, len</fromCalldata>) {
|
||||
<?fromStorage> if eq(slot, src) { leave } </fromStorage>
|
||||
|
||||
let newLen := <arrayLength>(src<?fromCalldata>, len</fromCalldata>)
|
||||
// Make sure array length is sane
|
||||
if gt(newLen, 0xffffffffffffffff) { <panic>() }
|
||||
@@ -1703,11 +1743,11 @@ string YulUtilFunctions::copyByteArrayToStorageFunction(ArrayType const& _fromTy
|
||||
let dstPtr := dstDataArea
|
||||
let i := 0
|
||||
for { } lt(i, loopEnd) { i := add(i, 32) } {
|
||||
sstore(dstPtr, <readFromCalldataOrMemory>(add(src, i)))
|
||||
sstore(dstPtr, <read>(add(src, i)))
|
||||
dstPtr := add(dstPtr, 1)
|
||||
}
|
||||
if lt(loopEnd, newLen) {
|
||||
let lastValue := <readFromCalldataOrMemory>(add(src, i))
|
||||
let lastValue := <read>(add(src, i))
|
||||
sstore(dstPtr, <maskBytes>(lastValue, and(newLen, 0x1f)))
|
||||
}
|
||||
sstore(slot, add(mul(newLen, 2), 1))
|
||||
@@ -1715,13 +1755,15 @@ string YulUtilFunctions::copyByteArrayToStorageFunction(ArrayType const& _fromTy
|
||||
default {
|
||||
let value := 0
|
||||
if newLen {
|
||||
value := <readFromCalldataOrMemory>(src)
|
||||
value := <read>(src)
|
||||
}
|
||||
sstore(slot, <byteArrayCombineShort>(value, newLen))
|
||||
}
|
||||
}
|
||||
)");
|
||||
templ("functionName", functionName);
|
||||
bool fromStorage = _fromType.dataStoredIn(DataLocation::Storage);
|
||||
templ("fromStorage", fromStorage);
|
||||
bool fromCalldata = _fromType.dataStoredIn(DataLocation::CallData);
|
||||
templ("fromMemory", _fromType.dataStoredIn(DataLocation::Memory));
|
||||
templ("fromCalldata", fromCalldata);
|
||||
@@ -1730,7 +1772,7 @@ string YulUtilFunctions::copyByteArrayToStorageFunction(ArrayType const& _fromTy
|
||||
templ("byteArrayLength", extractByteArrayLengthFunction());
|
||||
templ("dstDataLocation", arrayDataAreaFunction(_toType));
|
||||
templ("clearStorageRange", clearStorageRangeFunction(*_toType.baseType()));
|
||||
templ("readFromCalldataOrMemory", readFromMemoryOrCalldata(*TypeProvider::uint256(), fromCalldata));
|
||||
templ("read", fromStorage ? "sload" : fromCalldata ? "calldataload" : "mload");
|
||||
templ("maskBytes", maskBytesFunctionDynamic());
|
||||
templ("byteArrayCombineShort", shortByteArrayEncodeUsedAreaSetLengthFunction());
|
||||
|
||||
@@ -1738,6 +1780,65 @@ string YulUtilFunctions::copyByteArrayToStorageFunction(ArrayType const& _fromTy
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
string YulUtilFunctions::copyValueArrayStorageToStorageFunction(ArrayType const& _fromType, ArrayType const& _toType)
|
||||
{
|
||||
solAssert(
|
||||
*_fromType.copyForLocation(_toType.location(), _toType.isPointer()) == dynamic_cast<ReferenceType const&>(_toType),
|
||||
""
|
||||
);
|
||||
solAssert(!_fromType.isByteArray(), "");
|
||||
solAssert(_fromType.dataStoredIn(DataLocation::Storage) && _toType.baseType()->isValueType(), "");
|
||||
solAssert(_toType.dataStoredIn(DataLocation::Storage), "");
|
||||
|
||||
string functionName = "copy_array_to_storage_from_" + _fromType.identifier() + "_to_" + _toType.identifier();
|
||||
return m_functionCollector.createFunction(functionName, [&](){
|
||||
Whiskers templ(R"(
|
||||
function <functionName>(dst, src) {
|
||||
if eq(dst, src) { leave }
|
||||
let length := <arrayLength>(src)
|
||||
// Make sure array length is sane
|
||||
if gt(length, 0xffffffffffffffff) { <panic>() }
|
||||
<?isToDynamic>
|
||||
<resizeArray>(dst, length)
|
||||
</isToDynamic>
|
||||
|
||||
let srcPtr := <srcDataLocation>(src)
|
||||
|
||||
let dstPtr := <dstDataLocation>(dst)
|
||||
|
||||
let fullSlots := div(length, <itemsPerSlot>)
|
||||
let i := 0
|
||||
for { } lt(i, fullSlots) { i := add(i, 1) } {
|
||||
sstore(add(dstPtr, i), <maskFull>(sload(add(srcPtr, i))))
|
||||
}
|
||||
let spill := sub(length, mul(i, <itemsPerSlot>))
|
||||
if gt(spill, 0) {
|
||||
sstore(add(dstPtr, i), <maskBytes>(sload(add(srcPtr, i)), mul(spill, <bytesPerItem>)))
|
||||
}
|
||||
}
|
||||
)");
|
||||
if (_fromType.dataStoredIn(DataLocation::Storage))
|
||||
solAssert(!_fromType.isValueType(), "");
|
||||
templ("functionName", functionName);
|
||||
templ("isToDynamic", _toType.isDynamicallySized());
|
||||
if (_toType.isDynamicallySized())
|
||||
templ("resizeArray", resizeDynamicArrayFunction(_toType));
|
||||
templ("arrayLength",arrayLengthFunction(_fromType));
|
||||
templ("panic", panicFunction());
|
||||
templ("srcDataLocation", arrayDataAreaFunction(_fromType));
|
||||
templ("dstDataLocation", arrayDataAreaFunction(_toType));
|
||||
unsigned itemsPerSlot = 32 / _toType.storageStride();
|
||||
templ("itemsPerSlot", to_string(itemsPerSlot));
|
||||
templ("bytesPerItem", to_string(_toType.storageStride()));
|
||||
templ("maskFull", maskLowerOrderBytesFunction(itemsPerSlot * _toType.storageStride()));
|
||||
templ("maskBytes", maskLowerOrderBytesFunctionDynamic());
|
||||
|
||||
return templ.render();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
string YulUtilFunctions::arrayConvertLengthToSize(ArrayType const& _type)
|
||||
{
|
||||
string functionName = "array_convert_length_to_size_" + _type.identifier();
|
||||
@@ -2298,132 +2399,149 @@ string YulUtilFunctions::updateStorageValueFunction(
|
||||
("prepare", prepareStoreFunction(_toType))
|
||||
.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, <?dynamicOffset>offset, </dynamicOffset><value>) {
|
||||
<?dynamicOffset>if offset { <panic>() }</dynamicOffset>
|
||||
<copyArrayToStorage>(slot, <value>)
|
||||
}
|
||||
)");
|
||||
templ("functionName", functionName);
|
||||
templ("dynamicOffset", !_offset.has_value());
|
||||
templ("panic", panicFunction());
|
||||
templ("value", suffixedVariableNameList("value_", 0, _fromType.sizeOnStack()));
|
||||
templ("copyArrayToStorage", copyArrayToStorageFunction(
|
||||
dynamic_cast<ArrayType const&>(_fromType),
|
||||
dynamic_cast<ArrayType const&>(_toType)
|
||||
));
|
||||
|
||||
return templ.render();
|
||||
}
|
||||
else
|
||||
{
|
||||
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, "");
|
||||
solUnimplementedAssert(
|
||||
fromReferenceType->location() != DataLocation::Storage,
|
||||
"Copying from storage to storage is not yet implemented."
|
||||
);
|
||||
solAssert(toReferenceType->category() == fromReferenceType->category(), "");
|
||||
solAssert(_toType.category() == Type::Category::Struct, "");
|
||||
|
||||
if (_toType.category() == Type::Category::Array)
|
||||
{
|
||||
solAssert(_offset.value_or(0) == 0, "");
|
||||
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>) {
|
||||
<copyArrayToStorage>(slot, <value>)
|
||||
Whiskers templ(R"(
|
||||
function <functionName>(slot, <?dynamicOffset>offset, </dynamicOffset>value) {
|
||||
<?dynamicOffset>if offset { <panic>() }</dynamicOffset>
|
||||
<?fromStorage> if eq(slot, value) { leave } </fromStorage>
|
||||
<#member>
|
||||
{
|
||||
<updateMemberCall>
|
||||
}
|
||||
)");
|
||||
templ("functionName", functionName);
|
||||
templ("value", suffixedVariableNameList("value_", 0, _fromType.sizeOnStack()));
|
||||
templ("copyArrayToStorage", copyArrayToStorageFunction(
|
||||
dynamic_cast<ArrayType const&>(_fromType),
|
||||
dynamic_cast<ArrayType const&>(_toType)
|
||||
));
|
||||
</member>
|
||||
}
|
||||
)");
|
||||
templ("functionName", functionName);
|
||||
templ("dynamicOffset", !_offset.has_value());
|
||||
templ("panic", panicFunction());
|
||||
templ("fromStorage", fromStructType.dataStoredIn(DataLocation::Storage));
|
||||
|
||||
return templ.render();
|
||||
}
|
||||
else if (_toType.category() == Type::Category::Struct)
|
||||
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)
|
||||
{
|
||||
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, "");
|
||||
Type const& memberType = *structMembers[i].type;
|
||||
solAssert(memberType.memoryHeadSize() == 32, "");
|
||||
auto const& [slotDiff, offset] = toStructType.storageOffsetsOfMember(structMembers[i].name);
|
||||
|
||||
Whiskers templ(R"(
|
||||
function <functionName>(slot, value) {
|
||||
<#member>
|
||||
{
|
||||
<updateMemberCall>
|
||||
}
|
||||
</member>
|
||||
}
|
||||
)");
|
||||
templ("functionName", functionName);
|
||||
Whiskers t(R"(
|
||||
let memberSlot := add(slot, <memberStorageSlotDiff>)
|
||||
let memberSrcPtr := add(value, <memberOffset>)
|
||||
|
||||
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>
|
||||
<?fromCalldata>
|
||||
let <memberValues> :=
|
||||
<?dynamicallyEncodedMember>
|
||||
let <memberCalldataOffset> := <accessCalldataTail>(value, add(value, <memberOffset>))
|
||||
<accessCalldataTail>(value, memberSrcPtr)
|
||||
<!dynamicallyEncodedMember>
|
||||
let <memberCalldataOffset> := add(value, <memberOffset>)
|
||||
memberSrcPtr
|
||||
</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,
|
||||
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());
|
||||
t("memberValues", suffixedVariableNameList(
|
||||
"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();
|
||||
}
|
||||
templ("member", memberParams);
|
||||
<?isValueType>
|
||||
<memberValues> := <read>(<memberValues>)
|
||||
</isValueType>
|
||||
</fromCalldata>
|
||||
|
||||
return templ.render();
|
||||
<?fromMemory>
|
||||
let <memberValues> := <read>(memberSrcPtr)
|
||||
</fromMemory>
|
||||
|
||||
<?fromStorage>
|
||||
let <memberValues> :=
|
||||
<?isValueType>
|
||||
<read>(memberSrcPtr)
|
||||
<!isValueType>
|
||||
memberSrcPtr
|
||||
</isValueType>
|
||||
</fromStorage>
|
||||
|
||||
<updateStorageValue>(memberSlot, <memberValues>)
|
||||
)");
|
||||
bool fromCalldata = fromStructType.location() == DataLocation::CallData;
|
||||
t("fromCalldata", fromCalldata);
|
||||
bool fromMemory = fromStructType.location() == DataLocation::Memory;
|
||||
t("fromMemory", fromMemory);
|
||||
bool fromStorage = fromStructType.location() == DataLocation::Storage;
|
||||
t("fromStorage", fromStorage);
|
||||
t("isValueType", memberType.isValueType());
|
||||
t("memberValues", suffixedVariableNameList("memberValue_", 0, memberType.stackItems().size()));
|
||||
|
||||
t("memberStorageSlotDiff", slotDiff.str());
|
||||
if (fromCalldata)
|
||||
{
|
||||
t("memberOffset", to_string(fromStructType.calldataOffsetOfMember(structMembers[i].name)));
|
||||
t("dynamicallyEncodedMember", memberType.isDynamicallyEncoded());
|
||||
if (memberType.isDynamicallyEncoded())
|
||||
t("accessCalldataTail", accessCalldataTailFunction(memberType));
|
||||
if (memberType.isValueType())
|
||||
t("read", readFromCalldata(memberType));
|
||||
}
|
||||
else if (fromMemory)
|
||||
{
|
||||
t("memberOffset", fromStructType.memoryOffsetOfMember(structMembers[i].name).str());
|
||||
t("read", readFromMemory(memberType));
|
||||
}
|
||||
else if (fromStorage)
|
||||
{
|
||||
auto [srcSlotOffset, srcOffset] = fromStructType.storageOffsetsOfMember(structMembers[i].name);
|
||||
t("memberOffset", formatNumber(srcSlotOffset));
|
||||
if (memberType.isValueType())
|
||||
t("read", readFromStorageValueType(memberType, srcOffset, false));
|
||||
else
|
||||
solAssert(srcOffset == 0, "");
|
||||
|
||||
}
|
||||
t("memberStorageSlotOffset", to_string(offset));
|
||||
t("updateStorageValue", updateStorageValueFunction(
|
||||
memberType,
|
||||
*toStructMembers[i].type,
|
||||
optional<unsigned>{offset}
|
||||
));
|
||||
memberParams[i]["updateMemberCall"] = t.render();
|
||||
}
|
||||
else
|
||||
solAssert(false, "Invalid non-value type for assignment.");
|
||||
templ("member", memberParams);
|
||||
|
||||
return templ.render();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -106,6 +106,15 @@ public:
|
||||
/// signature: (value, bytes) -> result
|
||||
std::string maskBytesFunctionDynamic();
|
||||
|
||||
/// Zeroes out all bytes above the first ``_bytes`` lower order bytes.
|
||||
/// signature: (value) -> result
|
||||
std::string maskLowerOrderBytesFunction(size_t _bytes);
|
||||
|
||||
/// Zeroes out all bytes above the first ``bytes`` lower order bytes.
|
||||
/// @note ``bytes`` has to be small enough not to overflow ``8 * bytes``.
|
||||
/// signature: (value, bytes) -> result
|
||||
std::string maskLowerOrderBytesFunctionDynamic();
|
||||
|
||||
/// @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.
|
||||
/// signature: (value) -> result
|
||||
@@ -209,14 +218,18 @@ public:
|
||||
/// signature: (slot) ->
|
||||
std::string clearStorageArrayFunction(ArrayType const& _type);
|
||||
|
||||
/// @returns the name of a function that will copy array from calldata or memory to storage
|
||||
/// @returns the name of a function that will copy an array to storage
|
||||
/// signature (to_slot, from_ptr) ->
|
||||
std::string copyArrayToStorageFunction(ArrayType const& _fromType, ArrayType const& _toType);
|
||||
|
||||
/// @returns the name of a function that will copy a byte array from calldata or memory to storage
|
||||
/// @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);
|
||||
|
||||
/// @returns the name of a function that will copy an array of value types from storage to storage.
|
||||
/// signature (to_slot, from_slot) ->
|
||||
std::string copyValueArrayStorageToStorageFunction(ArrayType const& _fromType, ArrayType const& _toType);
|
||||
|
||||
/// Returns the name of a function that will convert a given length to the
|
||||
/// size in memory (number of storage slots or calldata/memory bytes) it
|
||||
/// will require.
|
||||
@@ -297,6 +310,7 @@ public:
|
||||
/// Returns the name of a function will write the given value to
|
||||
/// the specified slot and offset. If offset is not given, it is expected as
|
||||
/// runtime parameter.
|
||||
/// For reference types, offset is checked to be zero at runtime.
|
||||
/// signature: (slot, [offset,] value)
|
||||
std::string updateStorageValueFunction(
|
||||
Type const& _fromType,
|
||||
|
||||
@@ -2658,22 +2658,22 @@ void IRGeneratorForStatements::writeToLValue(IRLValue const& _lvalue, IRVariable
|
||||
std::visit(
|
||||
util::GenericVisitor{
|
||||
[&](IRLValue::Storage const& _storage) {
|
||||
std::optional<unsigned> offset;
|
||||
string offsetArgument;
|
||||
optional<unsigned> offsetStatic;
|
||||
|
||||
if (std::holds_alternative<unsigned>(_storage.offset))
|
||||
offset = std::get<unsigned>(_storage.offset);
|
||||
std::visit(GenericVisitor{
|
||||
[&](unsigned _offset) { offsetStatic = _offset; },
|
||||
[&](string const& _offset) { offsetArgument = ", " + _offset; }
|
||||
}, _storage.offset);
|
||||
|
||||
m_code <<
|
||||
m_utils.updateStorageValueFunction(_value.type(), _lvalue.type, offset) <<
|
||||
m_utils.updateStorageValueFunction(_value.type(), _lvalue.type, offsetStatic) <<
|
||||
"(" <<
|
||||
_storage.slot <<
|
||||
(
|
||||
std::holds_alternative<string>(_storage.offset) ?
|
||||
(", " + std::get<string>(_storage.offset)) :
|
||||
""
|
||||
) <<
|
||||
offsetArgument <<
|
||||
_value.commaSeparatedListPrefixed() <<
|
||||
")\n";
|
||||
|
||||
},
|
||||
[&](IRLValue::Memory const& _memory) {
|
||||
if (_lvalue.type.isValueType())
|
||||
|
||||
+22
-36
@@ -96,20 +96,7 @@ bool BMC::shouldInlineFunctionCall(FunctionCall const& _funCall)
|
||||
|
||||
FunctionType const& funType = dynamic_cast<FunctionType const&>(*_funCall.expression().annotation().type);
|
||||
if (funType.kind() == FunctionType::Kind::External)
|
||||
{
|
||||
auto memberAccess = dynamic_cast<MemberAccess const*>(&_funCall.expression());
|
||||
if (!memberAccess)
|
||||
return false;
|
||||
|
||||
auto identifier = dynamic_cast<Identifier const*>(&memberAccess->expression());
|
||||
if (!(
|
||||
identifier &&
|
||||
identifier->name() == "this" &&
|
||||
identifier->annotation().referencedDeclaration &&
|
||||
dynamic_cast<MagicVariableDeclaration const*>(identifier->annotation().referencedDeclaration)
|
||||
))
|
||||
return false;
|
||||
}
|
||||
return isTrustedExternalCall(&_funCall.expression());
|
||||
else if (funType.kind() != FunctionType::Kind::Internal)
|
||||
return false;
|
||||
|
||||
@@ -133,7 +120,10 @@ void BMC::endVisit(ContractDefinition const& _contract)
|
||||
constructor->accept(*this);
|
||||
else
|
||||
{
|
||||
/// Visiting implicit constructor - we need a dummy callstack frame
|
||||
pushCallStack({nullptr, nullptr});
|
||||
inlineConstructorHierarchy(_contract);
|
||||
popCallStack();
|
||||
/// Check targets created by state variable initialization.
|
||||
smtutil::Expression constraints = m_context.assertions();
|
||||
checkVerificationTargets(constraints);
|
||||
@@ -844,32 +834,28 @@ void BMC::checkCondition(
|
||||
{
|
||||
case smtutil::CheckResult::SATISFIABLE:
|
||||
{
|
||||
solAssert(!_callStack.empty(), "");
|
||||
std::ostringstream message;
|
||||
message << "BMC: " << _description << " happens here.";
|
||||
if (_callStack.size())
|
||||
{
|
||||
std::ostringstream modelMessage;
|
||||
modelMessage << "Counterexample:\n";
|
||||
solAssert(values.size() == expressionNames.size(), "");
|
||||
map<string, string> sortedModel;
|
||||
for (size_t i = 0; i < values.size(); ++i)
|
||||
if (expressionsToEvaluate.at(i).name != values.at(i))
|
||||
sortedModel[expressionNames.at(i)] = values.at(i);
|
||||
std::ostringstream modelMessage;
|
||||
modelMessage << "Counterexample:\n";
|
||||
solAssert(values.size() == expressionNames.size(), "");
|
||||
map<string, string> sortedModel;
|
||||
for (size_t i = 0; i < values.size(); ++i)
|
||||
if (expressionsToEvaluate.at(i).name != values.at(i))
|
||||
sortedModel[expressionNames.at(i)] = values.at(i);
|
||||
|
||||
for (auto const& eval: sortedModel)
|
||||
modelMessage << " " << eval.first << " = " << eval.second << "\n";
|
||||
for (auto const& eval: sortedModel)
|
||||
modelMessage << " " << eval.first << " = " << eval.second << "\n";
|
||||
|
||||
m_errorReporter.warning(
|
||||
_errorHappens,
|
||||
_location,
|
||||
message.str(),
|
||||
SecondarySourceLocation().append(modelMessage.str(), SourceLocation{})
|
||||
.append(SMTEncoder::callStackMessage(_callStack))
|
||||
.append(move(secondaryLocation))
|
||||
);
|
||||
}
|
||||
else
|
||||
m_errorReporter.warning(6084_error, _location, message.str(), secondaryLocation);
|
||||
m_errorReporter.warning(
|
||||
_errorHappens,
|
||||
_location,
|
||||
message.str(),
|
||||
SecondarySourceLocation().append(modelMessage.str(), SourceLocation{})
|
||||
.append(SMTEncoder::callStackMessage(_callStack))
|
||||
.append(move(secondaryLocation))
|
||||
);
|
||||
break;
|
||||
}
|
||||
case smtutil::CheckResult::UNSATISFIABLE:
|
||||
|
||||
+58
-14
@@ -562,8 +562,6 @@ void CHC::internalFunctionCall(FunctionCall const& _funCall)
|
||||
m_context.addAssertion(interface(*contract));
|
||||
}
|
||||
|
||||
auto previousError = errorFlag().currentValue();
|
||||
|
||||
m_context.addAssertion(predicate(_funCall));
|
||||
|
||||
connectBlocks(
|
||||
@@ -572,8 +570,6 @@ void CHC::internalFunctionCall(FunctionCall const& _funCall)
|
||||
(errorFlag().currentValue() > 0)
|
||||
);
|
||||
m_context.addAssertion(errorFlag().currentValue() == 0);
|
||||
errorFlag().increaseIndex();
|
||||
m_context.addAssertion(errorFlag().currentValue() == previousError);
|
||||
}
|
||||
|
||||
void CHC::externalFunctionCall(FunctionCall const& _funCall)
|
||||
@@ -583,6 +579,11 @@ void CHC::externalFunctionCall(FunctionCall const& _funCall)
|
||||
/// so we just add the nondet_interface predicate.
|
||||
|
||||
solAssert(m_currentContract, "");
|
||||
if (isTrustedExternalCall(&_funCall.expression()))
|
||||
{
|
||||
externalFunctionCallToTrustedCode(_funCall);
|
||||
return;
|
||||
}
|
||||
|
||||
FunctionType const& funType = dynamic_cast<FunctionType const&>(*_funCall.expression().annotation().type);
|
||||
auto kind = funType.kind();
|
||||
@@ -615,6 +616,42 @@ void CHC::externalFunctionCall(FunctionCall const& _funCall)
|
||||
m_context.addAssertion(errorFlag().currentValue() == 0);
|
||||
}
|
||||
|
||||
void CHC::externalFunctionCallToTrustedCode(FunctionCall const& _funCall)
|
||||
{
|
||||
solAssert(m_currentContract, "");
|
||||
FunctionType const& funType = dynamic_cast<FunctionType const&>(*_funCall.expression().annotation().type);
|
||||
auto kind = funType.kind();
|
||||
solAssert(kind == FunctionType::Kind::External || kind == FunctionType::Kind::BareStaticCall, "");
|
||||
|
||||
auto const* function = functionCallToDefinition(_funCall);
|
||||
if (!function)
|
||||
return;
|
||||
|
||||
// External call creates a new transaction.
|
||||
auto originalTx = state().tx();
|
||||
auto txOrigin = state().txMember("tx.origin");
|
||||
state().newTx();
|
||||
// set the transaction sender as this contract
|
||||
m_context.addAssertion(state().txMember("msg.sender") == state().thisAddress());
|
||||
// set the origin to be the current transaction origin
|
||||
m_context.addAssertion(state().txMember("tx.origin") == txOrigin);
|
||||
|
||||
smtutil::Expression pred = predicate(_funCall);
|
||||
|
||||
auto txConstraints = m_context.state().txConstraints(*function);
|
||||
m_context.addAssertion(pred && txConstraints);
|
||||
// restore the original transaction data
|
||||
state().newTx();
|
||||
m_context.addAssertion(originalTx == state().tx());
|
||||
|
||||
connectBlocks(
|
||||
m_currentBlock,
|
||||
(m_currentFunction && !m_currentFunction->isConstructor()) ? summary(*m_currentFunction) : summary(*m_currentContract),
|
||||
(errorFlag().currentValue() > 0)
|
||||
);
|
||||
m_context.addAssertion(errorFlag().currentValue() == 0);
|
||||
}
|
||||
|
||||
void CHC::unknownFunctionCall(FunctionCall const&)
|
||||
{
|
||||
/// Function calls are not handled at the moment,
|
||||
@@ -1011,27 +1048,34 @@ smtutil::Expression CHC::predicate(Predicate const& _block)
|
||||
|
||||
smtutil::Expression CHC::predicate(FunctionCall const& _funCall)
|
||||
{
|
||||
/// Used only for internal calls.
|
||||
FunctionType const& funType = dynamic_cast<FunctionType const&>(*_funCall.expression().annotation().type);
|
||||
auto kind = funType.kind();
|
||||
solAssert(kind == FunctionType::Kind::Internal || kind == FunctionType::Kind::External || kind == FunctionType::Kind::BareStaticCall, "");
|
||||
|
||||
auto const* function = functionCallToDefinition(_funCall);
|
||||
if (!function)
|
||||
return smtutil::Expression(true);
|
||||
|
||||
auto contractAddressValue = [this](FunctionCall const& _f) {
|
||||
FunctionType const& funType = dynamic_cast<FunctionType const&>(*_f.expression().annotation().type);
|
||||
if (funType.kind() == FunctionType::Kind::Internal)
|
||||
return state().thisAddress();
|
||||
if (MemberAccess const* callBase = dynamic_cast<MemberAccess const*>(&_f.expression()))
|
||||
return expr(callBase->expression());
|
||||
solAssert(false, "Unreachable!");
|
||||
};
|
||||
errorFlag().increaseIndex();
|
||||
vector<smtutil::Expression> args{errorFlag().currentValue(), state().thisAddress(), state().crypto(), state().tx(), state().state()};
|
||||
vector<smtutil::Expression> args{errorFlag().currentValue(), contractAddressValue(_funCall), state().crypto(), state().tx(), state().state()};
|
||||
|
||||
FunctionType const& funType = dynamic_cast<FunctionType const&>(*_funCall.expression().annotation().type);
|
||||
solAssert(funType.kind() == FunctionType::Kind::Internal, "");
|
||||
|
||||
/// Internal calls can be made to the contract itself or a library.
|
||||
auto const* contract = function->annotation().contract;
|
||||
auto const& hierarchy = m_currentContract->annotation().linearizedBaseContracts;
|
||||
solAssert(contract->isLibrary() || find(hierarchy.begin(), hierarchy.end(), contract) != hierarchy.end(), "");
|
||||
solAssert(kind != FunctionType::Kind::Internal || contract->isLibrary() || contains(hierarchy, contract), "");
|
||||
|
||||
/// If the call is to a library, we use that library as the called contract.
|
||||
/// If it is not, we use the current contract even if it is a call to a contract
|
||||
/// up in the inheritance hierarchy, since the interfaces/predicates are different.
|
||||
auto const* calledContract = contract->isLibrary() ? contract : m_currentContract;
|
||||
/// If the call is to a contract not in the inheritance hierarchy, we also use that as the called contract.
|
||||
/// Otherwise, the call is to some contract in the inheritance hierarchy of the current contract.
|
||||
/// In this case we use current contract as the called one since the interfaces/predicates are different.
|
||||
auto const* calledContract = contains(hierarchy, contract) ? m_currentContract : contract;
|
||||
solAssert(calledContract, "");
|
||||
|
||||
bool usesStaticCall = function->stateMutability() == StateMutability::Pure || function->stateMutability() == StateMutability::View;
|
||||
|
||||
@@ -88,6 +88,7 @@ private:
|
||||
void visitAddMulMod(FunctionCall const& _funCall) override;
|
||||
void internalFunctionCall(FunctionCall const& _funCall);
|
||||
void externalFunctionCall(FunctionCall const& _funCall);
|
||||
void externalFunctionCallToTrustedCode(FunctionCall const& _funCall);
|
||||
void unknownFunctionCall(FunctionCall const& _funCall);
|
||||
void makeArrayPopVerificationTarget(FunctionCall const& _arrayPop) override;
|
||||
/// Creates underflow/overflow verification targets.
|
||||
|
||||
@@ -2386,6 +2386,19 @@ MemberAccess const* SMTEncoder::isEmptyPush(Expression const& _expr) const
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool SMTEncoder::isTrustedExternalCall(Expression const* _expr) {
|
||||
auto memberAccess = dynamic_cast<MemberAccess const*>(_expr);
|
||||
if (!memberAccess)
|
||||
return false;
|
||||
|
||||
auto identifier = dynamic_cast<Identifier const*>(&memberAccess->expression());
|
||||
return identifier &&
|
||||
identifier->name() == "this" &&
|
||||
identifier->annotation().referencedDeclaration &&
|
||||
dynamic_cast<MagicVariableDeclaration const*>(identifier->annotation().referencedDeclaration)
|
||||
;
|
||||
}
|
||||
|
||||
string SMTEncoder::extraComment()
|
||||
{
|
||||
string extra;
|
||||
|
||||
@@ -295,6 +295,10 @@ protected:
|
||||
/// otherwise nullptr.
|
||||
MemberAccess const* isEmptyPush(Expression const& _expr) const;
|
||||
|
||||
/// @returns true if the given identifier is a contract which is known and trusted.
|
||||
/// This means we don't have to abstract away effects of external function calls to this contract.
|
||||
static bool isTrustedExternalCall(Expression const* _expr);
|
||||
|
||||
/// Creates symbolic expressions for the returned values
|
||||
/// and set them as the components of the symbolic tuple.
|
||||
void createReturnedExpressions(FunctionCall const& _funCall);
|
||||
|
||||
@@ -131,6 +131,13 @@ void CompilerStack::setRemappings(vector<Remapping> const& _remappings)
|
||||
m_remappings = _remappings;
|
||||
}
|
||||
|
||||
void CompilerStack::setViaIR(bool _viaIR)
|
||||
{
|
||||
if (m_stackState >= ParsedAndImported)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must set viaIR before parsing."));
|
||||
m_viaIR = _viaIR;
|
||||
}
|
||||
|
||||
void CompilerStack::setEVMVersion(langutil::EVMVersion _version)
|
||||
{
|
||||
if (m_stackState >= ParsedAndImported)
|
||||
@@ -213,6 +220,7 @@ void CompilerStack::reset(bool _keepSettings)
|
||||
{
|
||||
m_remappings.clear();
|
||||
m_libraries.clear();
|
||||
m_viaIR = false;
|
||||
m_evmVersion = langutil::EVMVersion();
|
||||
m_modelCheckerSettings = ModelCheckerSettings{};
|
||||
m_enabledSMTSolvers = smtutil::SMTSolverChoice::All();
|
||||
@@ -532,10 +540,15 @@ bool CompilerStack::compile(State _stopAfter)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (m_generateEvmBytecode)
|
||||
compileContract(*contract, otherCompilers);
|
||||
if (m_generateIR || m_generateEwasm)
|
||||
if (m_viaIR || m_generateIR || m_generateEwasm)
|
||||
generateIR(*contract);
|
||||
if (m_generateEvmBytecode)
|
||||
{
|
||||
if (m_viaIR)
|
||||
generateEVMFromIR(*contract);
|
||||
else
|
||||
compileContract(*contract, otherCompilers);
|
||||
}
|
||||
if (m_generateEwasm)
|
||||
generateEwasm(*contract);
|
||||
}
|
||||
@@ -1250,12 +1263,45 @@ void CompilerStack::generateIR(ContractDefinition const& _contract)
|
||||
tie(compiledContract.yulIR, compiledContract.yulIROptimized) = generator.run(_contract, otherYulSources);
|
||||
}
|
||||
|
||||
void CompilerStack::generateEVMFromIR(ContractDefinition const& _contract)
|
||||
{
|
||||
solAssert(m_stackState >= AnalysisPerformed, "");
|
||||
if (m_hasError)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Called generateEVMFromIR with errors."));
|
||||
|
||||
if (!_contract.canBeDeployed())
|
||||
return;
|
||||
|
||||
Contract& compiledContract = m_contracts.at(_contract.fullyQualifiedName());
|
||||
solAssert(!compiledContract.yulIROptimized.empty(), "");
|
||||
if (!compiledContract.object.bytecode.empty())
|
||||
return;
|
||||
|
||||
// Re-parse the Yul IR in EVM dialect
|
||||
yul::AssemblyStack stack(m_evmVersion, yul::AssemblyStack::Language::StrictAssembly, m_optimiserSettings);
|
||||
stack.parseAndAnalyze("", compiledContract.yulIROptimized);
|
||||
stack.optimize();
|
||||
|
||||
//cout << yul::AsmPrinter{}(*stack.parserResult()->code) << endl;
|
||||
|
||||
// TODO: support passing metadata
|
||||
auto result = stack.assemble(yul::AssemblyStack::Machine::EVM);
|
||||
compiledContract.object = std::move(*result.bytecode);
|
||||
// TODO: support runtimeObject
|
||||
// TODO: add EIP-170 size check for runtimeObject
|
||||
// TODO: refactor assemblyItems, runtimeAssemblyItems, generatedSources,
|
||||
// assemblyString, assemblyJSON, and functionEntryPoints to work with this code path
|
||||
}
|
||||
|
||||
void CompilerStack::generateEwasm(ContractDefinition const& _contract)
|
||||
{
|
||||
solAssert(m_stackState >= AnalysisPerformed, "");
|
||||
if (m_hasError)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Called generateEwasm with errors."));
|
||||
|
||||
if (!_contract.canBeDeployed())
|
||||
return;
|
||||
|
||||
Contract& compiledContract = m_contracts.at(_contract.fullyQualifiedName());
|
||||
solAssert(!compiledContract.yulIROptimized.empty(), "");
|
||||
if (!compiledContract.ewasm.empty())
|
||||
@@ -1392,6 +1438,8 @@ string CompilerStack::createMetadata(Contract const& _contract) const
|
||||
static vector<string> hashes{"ipfs", "bzzr1", "none"};
|
||||
meta["settings"]["metadata"]["bytecodeHash"] = hashes.at(unsigned(m_metadataHash));
|
||||
|
||||
if (m_viaIR)
|
||||
meta["settings"]["viaIR"] = m_viaIR;
|
||||
meta["settings"]["evmVersion"] = m_evmVersion.name();
|
||||
meta["settings"]["compilationTarget"][_contract.contract->sourceUnitName()] =
|
||||
*_contract.contract->annotation().canonicalName;
|
||||
@@ -1514,7 +1562,7 @@ bytes CompilerStack::createCBORMetadata(Contract const& _contract) const
|
||||
else
|
||||
solAssert(m_metadataHash == MetadataHash::None, "Invalid metadata hash");
|
||||
|
||||
if (experimentalMode)
|
||||
if (experimentalMode || m_viaIR)
|
||||
encoder.pushBool("experimental", true);
|
||||
if (m_release)
|
||||
encoder.pushBytes("solc", VersionCompactBytes);
|
||||
|
||||
@@ -162,6 +162,10 @@ public:
|
||||
m_parserErrorRecovery = _wantErrorRecovery;
|
||||
}
|
||||
|
||||
/// Sets the pipeline to go through the Yul IR or not.
|
||||
/// Must be set before parsing.
|
||||
void setViaIR(bool _viaIR);
|
||||
|
||||
/// Set the EVM version used before running compile.
|
||||
/// When called without an argument it will revert to the default version.
|
||||
/// Must be set before parsing.
|
||||
@@ -399,7 +403,12 @@ private:
|
||||
/// The IR is stored but otherwise unused.
|
||||
void generateIR(ContractDefinition const& _contract);
|
||||
|
||||
/// Generate EVM representation for a single contract.
|
||||
/// Depends on output generated by generateIR.
|
||||
void generateEVMFromIR(ContractDefinition const& _contract);
|
||||
|
||||
/// Generate Ewasm representation for a single contract.
|
||||
/// Depends on output generated by generateIR.
|
||||
void generateEwasm(ContractDefinition const& _contract);
|
||||
|
||||
/// Links all the known library addresses in the available objects. Any unknown
|
||||
@@ -455,6 +464,7 @@ private:
|
||||
OptimiserSettings m_optimiserSettings;
|
||||
RevertStrings m_revertStrings = RevertStrings::Default;
|
||||
State m_stopAfter = State::CompilationSuccessful;
|
||||
bool m_viaIR = false;
|
||||
langutil::EVMVersion m_evmVersion;
|
||||
ModelCheckerSettings m_modelCheckerSettings;
|
||||
smtutil::SMTSolverChoice m_enabledSMTSolvers;
|
||||
|
||||
@@ -323,10 +323,12 @@ Json::Value formatLinkReferences(std::map<size_t, std::string> const& linkRefere
|
||||
for (auto const& ref: linkReferences)
|
||||
{
|
||||
string const& fullname = ref.second;
|
||||
|
||||
// If the link reference does not contain a colon, assume that the file name is missing and
|
||||
// the whole string represents the library name.
|
||||
size_t colon = fullname.rfind(':');
|
||||
solAssert(colon != string::npos, "");
|
||||
string file = fullname.substr(0, colon);
|
||||
string name = fullname.substr(colon + 1);
|
||||
string file = (colon != string::npos ? fullname.substr(0, colon) : "");
|
||||
string name = (colon != string::npos ? fullname.substr(colon + 1) : fullname);
|
||||
|
||||
Json::Value fileObject = ret.get(file, Json::objectValue);
|
||||
Json::Value libraryArray = fileObject.get(name, Json::arrayValue);
|
||||
@@ -414,7 +416,7 @@ std::optional<Json::Value> checkAuxiliaryInputKeys(Json::Value const& _input)
|
||||
|
||||
std::optional<Json::Value> checkSettingsKeys(Json::Value const& _input)
|
||||
{
|
||||
static set<string> keys{"parserErrorRecovery", "debug", "evmVersion", "libraries", "metadata", "optimizer", "outputSelection", "remappings", "stopAfter"};
|
||||
static set<string> keys{"parserErrorRecovery", "debug", "evmVersion", "libraries", "metadata", "optimizer", "outputSelection", "remappings", "stopAfter", "viaIR"};
|
||||
return checkKeys(_input, keys, "settings");
|
||||
}
|
||||
|
||||
@@ -749,6 +751,13 @@ std::variant<StandardCompiler::InputsAndSettings, Json::Value> StandardCompiler:
|
||||
ret.parserErrorRecovery = settings["parserErrorRecovery"].asBool();
|
||||
}
|
||||
|
||||
if (settings.isMember("viaIR"))
|
||||
{
|
||||
if (!settings["viaIR"].isBool())
|
||||
return formatFatalError("JSONError", "\"settings.viaIR\" must be a Boolean.");
|
||||
ret.viaIR = settings["viaIR"].asBool();
|
||||
}
|
||||
|
||||
if (settings.isMember("evmVersion"))
|
||||
{
|
||||
if (!settings["evmVersion"].isString())
|
||||
@@ -830,8 +839,7 @@ std::variant<StandardCompiler::InputsAndSettings, Json::Value> StandardCompiler:
|
||||
|
||||
try
|
||||
{
|
||||
// @TODO use libraries only for the given source
|
||||
ret.libraries[library] = util::h160(address);
|
||||
ret.libraries[sourceName + ":" + library] = util::h160(address);
|
||||
}
|
||||
catch (util::BadHexCharacter const&)
|
||||
{
|
||||
@@ -906,6 +914,7 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting
|
||||
compilerStack.setSources(sourceList);
|
||||
for (auto const& smtLib2Response: _inputsAndSettings.smtLib2Responses)
|
||||
compilerStack.addSMTLib2Response(smtLib2Response.first, smtLib2Response.second);
|
||||
compilerStack.setViaIR(_inputsAndSettings.viaIR);
|
||||
compilerStack.setEVMVersion(_inputsAndSettings.evmVersion);
|
||||
compilerStack.setParserErrorRecovery(_inputsAndSettings.parserErrorRecovery);
|
||||
compilerStack.setRemappings(_inputsAndSettings.remappings);
|
||||
|
||||
@@ -72,6 +72,7 @@ private:
|
||||
CompilerStack::MetadataHash metadataHash = CompilerStack::MetadataHash::IPFS;
|
||||
Json::Value outputSelection;
|
||||
ModelCheckerSettings modelCheckerSettings = ModelCheckerSettings{};
|
||||
bool viaIR = false;
|
||||
};
|
||||
|
||||
/// Parses the input json (and potentially invokes the read callback) and either returns
|
||||
|
||||
Reference in New Issue
Block a user