/* This file is part of solidity. solidity is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. solidity is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with solidity. If not, see . */ /** * Component that can generate various useful Yul functions. */ #include #include #include #include #include #include #include using namespace std; using namespace dev; using namespace dev::solidity; string YulUtilFunctions::combineExternalFunctionIdFunction() { string functionName = "combine_external_function_id"; return m_functionCollector->createFunction(functionName, [&]() { return Whiskers(R"( function (addr, selector) -> combined { combined := (or((addr), and(selector, 0xffffffff))) } )") ("functionName", functionName) ("shl32", shiftLeftFunction(32)) ("shl64", shiftLeftFunction(64)) .render(); }); } string YulUtilFunctions::splitExternalFunctionIdFunction() { string functionName = "split_external_function_id"; return m_functionCollector->createFunction(functionName, [&]() { return Whiskers(R"( function (combined) -> addr, selector { combined := (combined) selector := and(combined, 0xffffffff) addr := (combined) } )") ("functionName", functionName) ("shr32", shiftRightFunction(32)) ("shr64", shiftRightFunction(64)) .render(); }); } string YulUtilFunctions::copyToMemoryFunction(bool _fromCalldata) { string functionName = "copy_" + string(_fromCalldata ? "calldata" : "memory") + "_to_memory"; return m_functionCollector->createFunction(functionName, [&]() { if (_fromCalldata) { return Whiskers(R"( function (src, dst, length) { calldatacopy(dst, src, length) // clear end mstore(add(dst, length), 0) } )") ("functionName", functionName) .render(); } else { return Whiskers(R"( function (src, dst, length) { let i := 0 for { } lt(i, length) { i := add(i, 32) } { mstore(add(dst, i), mload(add(src, i))) } if gt(i, length) { // clear end mstore(add(dst, length), 0) } } )") ("functionName", functionName) .render(); } }); } string YulUtilFunctions::requireOrAssertFunction(bool _assert, Type const* _messageType) { string functionName = string(_assert ? "assert_helper" : "require_helper") + (_messageType ? ("_" + _messageType->identifier()) : ""); solAssert(!_assert || !_messageType, "Asserts can't have messages!"); return m_functionCollector->createFunction(functionName, [&]() { if (!_messageType) return Whiskers(R"( function (condition) { if iszero(condition) { } } )") ("invalidOrRevert", _assert ? "invalid()" : "revert(0, 0)") ("functionName", functionName) .render(); int const hashHeaderSize = 4; int const byteSize = 8; u256 const errorHash = u256(FixedHash::Arith( FixedHash(dev::keccak256("Error(string)")) )) << (256 - hashHeaderSize * byteSize); string const encodeFunc = ABIFunctions(m_evmVersion, m_functionCollector) .tupleEncoder( {_messageType}, {TypeProvider::stringMemory()} ); return Whiskers(R"( function (condition ) { if iszero(condition) { let fmp := mload() mstore(fmp, ) let end := (add(fmp, ) ) revert(fmp, sub(end, fmp)) } } )") ("functionName", functionName) ("freeMemPointer", to_string(CompilerUtils::freeMemoryPointer)) ("errorHash", formatNumber(errorHash)) ("abiEncodeFunc", encodeFunc) ("hashHeaderSize", to_string(hashHeaderSize)) ("messageVars", (_messageType->sizeOnStack() > 0 ? ", " : "") + suffixedVariableNameList("message_", 1, 1 + _messageType->sizeOnStack()) ) .render(); }); } string YulUtilFunctions::leftAlignFunction(Type const& _type) { string functionName = string("leftAlign_") + _type.identifier(); return m_functionCollector->createFunction(functionName, [&]() { Whiskers templ(R"( function (value) -> aligned { } )"); templ("functionName", functionName); switch (_type.category()) { case Type::Category::Address: templ("body", "aligned := " + leftAlignFunction(IntegerType(160)) + "(value)"); break; case Type::Category::Integer: { IntegerType const& type = dynamic_cast(_type); if (type.numBits() == 256) templ("body", "aligned := value"); else templ("body", "aligned := " + shiftLeftFunction(256 - type.numBits()) + "(value)"); break; } case Type::Category::RationalNumber: solAssert(false, "Left align requested for rational number."); break; case Type::Category::Bool: templ("body", "aligned := " + leftAlignFunction(IntegerType(8)) + "(value)"); break; case Type::Category::FixedPoint: solUnimplemented("Fixed point types not implemented."); break; case Type::Category::Array: case Type::Category::Struct: solAssert(false, "Left align requested for non-value type."); break; case Type::Category::FixedBytes: templ("body", "aligned := value"); break; case Type::Category::Contract: templ("body", "aligned := " + leftAlignFunction(*TypeProvider::address()) + "(value)"); break; case Type::Category::Enum: { unsigned storageBytes = dynamic_cast(_type).storageBytes(); templ("body", "aligned := " + leftAlignFunction(IntegerType(8 * storageBytes)) + "(value)"); break; } case Type::Category::InaccessibleDynamic: solAssert(false, "Left align requested for inaccessible dynamic type."); break; default: solAssert(false, "Left align of type " + _type.identifier() + " requested."); } return templ.render(); }); } string YulUtilFunctions::shiftLeftFunction(size_t _numBits) { solAssert(_numBits < 256, ""); string functionName = "shift_left_" + to_string(_numBits); return m_functionCollector->createFunction(functionName, [&]() { return Whiskers(R"( function (value) -> newValue { newValue := shl(, value) mul(value, ) } )") ("functionName", functionName) ("numBits", to_string(_numBits)) ("hasShifts", m_evmVersion.hasBitwiseShifting()) ("multiplier", toCompactHexWithPrefix(u256(1) << _numBits)) .render(); }); } string YulUtilFunctions::shiftLeftFunctionDynamic() { string functionName = "shift_left_dynamic"; return m_functionCollector->createFunction(functionName, [&]() { return Whiskers(R"( function (bits, value) -> newValue { newValue := shl(bits, value) mul(value, exp(2, bits)) } )") ("functionName", functionName) ("hasShifts", m_evmVersion.hasBitwiseShifting()) .render(); }); } string YulUtilFunctions::shiftRightFunction(size_t _numBits) { solAssert(_numBits < 256, ""); // Note that if this is extended with signed shifts, // the opcodes SAR and SDIV behave differently with regards to rounding! string functionName = "shift_right_" + to_string(_numBits) + "_unsigned"; return m_functionCollector->createFunction(functionName, [&]() { return Whiskers(R"( function (value) -> newValue { newValue := shr(, value) div(value, ) } )") ("functionName", functionName) ("hasShifts", m_evmVersion.hasBitwiseShifting()) ("numBits", to_string(_numBits)) ("multiplier", toCompactHexWithPrefix(u256(1) << _numBits)) .render(); }); } string YulUtilFunctions::shiftRightFunctionDynamic() { // Note that if this is extended with signed shifts, // the opcodes SAR and SDIV behave differently with regards to rounding! string const functionName = "shift_right_unsigned_dynamic"; return m_functionCollector->createFunction(functionName, [&]() { return Whiskers(R"( function (bits, value) -> newValue { newValue := shr(bits, value) div(value, exp(2, bits)) } )") ("functionName", functionName) ("hasShifts", m_evmVersion.hasBitwiseShifting()) .render(); }); } string YulUtilFunctions::updateByteSliceFunction(size_t _numBytes, size_t _shiftBytes) { solAssert(_numBytes <= 32, ""); solAssert(_shiftBytes <= 32, ""); size_t numBits = _numBytes * 8; size_t shiftBits = _shiftBytes * 8; string functionName = "update_byte_slice_" + to_string(_numBytes) + "_shift_" + to_string(_shiftBytes); return m_functionCollector->createFunction(functionName, [&]() { return Whiskers(R"( function (value, toInsert) -> result { let mask := toInsert := (toInsert) value := and(value, not(mask)) result := or(value, and(toInsert, mask)) } )") ("functionName", functionName) ("mask", formatNumber(((bigint(1) << numBits) - 1) << shiftBits)) ("shl", shiftLeftFunction(shiftBits)) .render(); }); } string YulUtilFunctions::updateByteSliceFunctionDynamic(size_t _numBytes) { solAssert(_numBytes <= 32, ""); size_t numBits = _numBytes * 8; string functionName = "update_byte_slice_dynamic" + to_string(_numBytes); return m_functionCollector->createFunction(functionName, [&]() { return Whiskers(R"( function (value, shiftBytes, toInsert) -> result { let shiftBits := mul(shiftBytes, 8) let mask := (shiftBits, ) toInsert := (shiftBits, toInsert) value := and(value, not(mask)) result := or(value, and(toInsert, mask)) } )") ("functionName", functionName) ("mask", formatNumber((bigint(1) << numBits) - 1)) ("shl", shiftLeftFunctionDynamic()) .render(); }); } string YulUtilFunctions::roundUpFunction() { string functionName = "round_up_to_mul_of_32"; return m_functionCollector->createFunction(functionName, [&]() { return Whiskers(R"( function (value) -> result { result := and(add(value, 31), not(31)) } )") ("functionName", functionName) .render(); }); } string YulUtilFunctions::overflowCheckedIntAddFunction(IntegerType const& _type) { string functionName = "checked_add_" + _type.identifier(); // TODO: Consider to add a special case for unsigned 256-bit integers // and use the following instead: // sum := add(x, y) if lt(sum, x) { revert(0, 0) } return m_functionCollector->createFunction(functionName, [&]() { return Whiskers(R"( function (x, y) -> sum { // overflow, if x >= 0 and y > (maxValue - x) if and(iszero(slt(x, 0)), sgt(y, sub(, x))) { revert(0, 0) } // underflow, if x < 0 and y < (minValue - x) if and(slt(x, 0), slt(y, sub(, x))) { revert(0, 0) } // overflow, if x > (maxValue - y) if gt(x, sub(, y)) { revert(0, 0) } sum := add(x, y) } )") ("functionName", functionName) ("signed", _type.isSigned()) ("maxValue", toCompactHexWithPrefix(u256(_type.maxValue()))) ("minValue", toCompactHexWithPrefix(u256(_type.minValue()))) .render(); }); } string YulUtilFunctions::overflowCheckedIntMulFunction(IntegerType const& _type) { string functionName = "checked_mul_" + _type.identifier(); return m_functionCollector->createFunction(functionName, [&]() { return // Multiplication by zero could be treated separately and directly return zero. Whiskers(R"( function (x, y) -> product { // overflow, if x > 0, y > 0 and x > (maxValue / y) if and(and(sgt(x, 0), sgt(y, 0)), gt(x, div(, y))) { revert(0, 0) } // underflow, if x > 0, y < 0 and y < (minValue / x) if and(and(sgt(x, 0), slt(y, 0)), slt(y, sdiv(, x))) { revert(0, 0) } // underflow, if x < 0, y > 0 and x < (minValue / y) if and(and(slt(x, 0), sgt(y, 0)), slt(x, sdiv(, y))) { revert(0, 0) } // overflow, if x < 0, y < 0 and x < (maxValue / y) if and(and(slt(x, 0), slt(y, 0)), slt(x, sdiv(, y))) { revert(0, 0) } // overflow, if x != 0 and y > (maxValue / x) if and(iszero(iszero(x)), gt(y, div(, x))) { revert(0, 0) } product := mul(x, y) } )") ("functionName", functionName) ("signed", _type.isSigned()) ("maxValue", toCompactHexWithPrefix(u256(_type.maxValue()))) ("minValue", toCompactHexWithPrefix(u256(_type.minValue()))) .render(); }); } string YulUtilFunctions::overflowCheckedIntDivFunction(IntegerType const& _type) { string functionName = "checked_div_" + _type.identifier(); return m_functionCollector->createFunction(functionName, [&]() { return Whiskers(R"( function (x, y) -> r { if iszero(y) { revert(0, 0) } // overflow for minVal / -1 if and( eq(x, ), eq(y, sub(0, 1)) ) { revert(0, 0) } r := sdiv(x, y) } )") ("functionName", functionName) ("signed", _type.isSigned()) ("minVal", toCompactHexWithPrefix(u256(_type.minValue()))) .render(); }); } string YulUtilFunctions::checkedIntModFunction(IntegerType const& _type) { string functionName = "checked_mod_" + _type.identifier(); return m_functionCollector->createFunction(functionName, [&]() { return Whiskers(R"( function (x, y) -> r { if iszero(y) { revert(0, 0) } r := smod(x, y) } )") ("functionName", functionName) ("signed", _type.isSigned()) .render(); }); } string YulUtilFunctions::overflowCheckedIntSubFunction(IntegerType const& _type) { string functionName = "checked_sub_" + _type.identifier(); return m_functionCollector->createFunction(functionName, [&] { return Whiskers(R"( function (x, y) -> diff { // underflow, if y >= 0 and x < (minValue + y) if and(iszero(slt(y, 0)), slt(x, add(, y))) { revert(0, 0) } // overflow, if y < 0 and x > (maxValue + y) if and(slt(y, 0), sgt(x, add(, y))) { revert(0, 0) } if lt(x, y) { revert(0, 0) } diff := sub(x, y) } )") ("functionName", functionName) ("signed", _type.isSigned()) ("maxValue", toCompactHexWithPrefix(u256(_type.maxValue()))) ("minValue", toCompactHexWithPrefix(u256(_type.minValue()))) .render(); }); } string YulUtilFunctions::arrayLengthFunction(ArrayType const& _type) { string functionName = "array_length_" + _type.identifier(); return m_functionCollector->createFunction(functionName, [&]() { Whiskers w(R"( function (value) -> length { length := mload(value) length := sload(value) // Retrieve length both for in-place strings and off-place strings: // Computes (x & (0x100 * (ISZERO (x & 1)) - 1)) / 2 // i.e. for short strings (x & 1 == 0) it does (x & 0xff) / 2 and for long strings it // computes (x & (-1)) / 2, which is equivalent to just x / 2. let mask := sub(mul(0x100, iszero(and(length, 1))), 1) length := div(and(length, mask), 2) length := } )"); w("functionName", functionName); w("dynamic", _type.isDynamicallySized()); if (!_type.isDynamicallySized()) w("length", toCompactHexWithPrefix(_type.length())); w("memory", _type.location() == DataLocation::Memory); w("storage", _type.location() == DataLocation::Storage); w("byteArray", _type.isByteArray()); if (_type.isDynamicallySized()) solAssert( _type.location() != DataLocation::CallData, "called regular array length function on calldata array" ); return w.render(); }); } 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()->isValueType(), "..."); solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "..."); solUnimplementedAssert(_type.baseType()->storageSize() == 1, ""); string functionName = "resize_array_" + _type.identifier(); return m_functionCollector->createFunction(functionName, [&]() { return Whiskers(R"( function (array, newLen) { if gt(newLen, ) { invalid() } let oldLen := (array) // Store new length sstore(array, newLen) // Size was reduced, clear end of array if lt(newLen, oldLen) { let oldSlotCount := (oldLen) let newSlotCount := (newLen) let arrayDataStart := (array) let deleteStart := add(arrayDataStart, newSlotCount) let deleteEnd := add(arrayDataStart, oldSlotCount) (deleteStart, deleteEnd) } })") ("functionName", functionName) ("fetchLength", arrayLengthFunction(_type)) ("convertToSize", arrayConvertLengthToSize(_type)) ("dataPosition", arrayDataAreaFunction(_type)) ("clearStorageRange", clearStorageRangeFunction(*_type.baseType())) ("maxArrayLength", (u256(1) << 64).str()) .render(); }); } string YulUtilFunctions::clearStorageRangeFunction(Type const& _type) { string functionName = "clear_storage_range_" + _type.identifier(); solUnimplementedAssert(_type.isValueType(), "..."); return m_functionCollector->createFunction(functionName, [&]() { return Whiskers(R"( function (start, end) { for {} lt(start, end) { start := add(start, 1) } { sstore(start, 0) } } )") ("functionName", functionName) .render(); }); } string YulUtilFunctions::arrayConvertLengthToSize(ArrayType const& _type) { string functionName = "array_convert_length_to_size_" + _type.identifier(); return m_functionCollector->createFunction(functionName, [&]() { Type const& baseType = *_type.baseType(); switch (_type.location()) { case DataLocation::Storage: { unsigned const baseStorageBytes = baseType.storageBytes(); solAssert(baseStorageBytes > 0, ""); solAssert(32 / baseStorageBytes > 0, ""); return Whiskers(R"( function (length) -> size { size := length size := (, length) // Number of slots rounded up size := div(add(length, sub(, 1)), ) })") ("functionName", functionName) ("multiSlot", baseType.storageSize() > 1) ("itemsPerSlot", to_string(32 / baseStorageBytes)) ("storageSize", baseType.storageSize().str()) ("mul", overflowCheckedIntMulFunction(*TypeProvider::uint256())) .render(); } case DataLocation::CallData: // fallthrough case DataLocation::Memory: return Whiskers(R"( function (length) -> size { size := length size := (length, ) })") ("functionName", functionName) ("elementSize", _type.location() == DataLocation::Memory ? baseType.memoryHeadSize() : baseType.calldataEncodedSize()) ("byteArray", _type.isByteArray()) ("mul", overflowCheckedIntMulFunction(*TypeProvider::uint256())) .render(); default: solAssert(false, ""); } }); } string YulUtilFunctions::arrayAllocationSizeFunction(ArrayType const& _type) { solAssert(_type.dataStoredIn(DataLocation::Memory), ""); string functionName = "array_allocation_size_" + _type.identifier(); return m_functionCollector->createFunction(functionName, [&]() { Whiskers w(R"( function (length) -> size { // Make sure we can allocate memory without overflow if gt(length, 0xffffffffffffffff) { revert(0, 0) } // round up size := and(add(length, 0x1f), not(0x1f)) size := mul(length, 0x20) // add length slot size := add(size, 0x20) } )"); w("functionName", functionName); w("byteArray", _type.isByteArray()); w("dynamic", _type.isDynamicallySized()); return w.render(); }); } string YulUtilFunctions::arrayDataAreaFunction(ArrayType const& _type) { string functionName = "array_dataslot_" + _type.identifier(); return m_functionCollector->createFunction(functionName, [&]() { // No special processing for calldata arrays, because they are stored as // offset of the data area and length on the stack, so the offset already // points to the data area. // This might change, if calldata arrays are stored in a single // stack slot at some point. return Whiskers(R"( function (ptr) -> data { data := ptr data := add(ptr, 0x20) mstore(0, ptr) data := keccak256(0, 0x20) } )") ("functionName", functionName) ("dynamic", _type.isDynamicallySized()) ("memory", _type.location() == DataLocation::Memory) ("storage", _type.location() == DataLocation::Storage) .render(); }); } string YulUtilFunctions::storageArrayIndexAccessFunction(ArrayType const& _type) { solUnimplementedAssert(_type.baseType()->storageBytes() > 16, ""); string functionName = "storage_array_index_access_" + _type.identifier(); return m_functionCollector->createFunction(functionName, [&]() { return Whiskers(R"( function (array, index) -> slot, offset { if iszero(lt(index, (array))) { invalid() } let data := (array) slot := add(data, mul(index, )) offset := 0 } )") ("functionName", functionName) ("arrayLen", arrayLengthFunction(_type)) ("dataAreaFunc", arrayDataAreaFunction(_type)) ("multipleItemsPerSlot", _type.baseType()->storageBytes() <= 16) ("storageSize", _type.baseType()->storageSize().str()) .render(); }); } string YulUtilFunctions::nextArrayElementFunction(ArrayType const& _type) { solAssert(!_type.isByteArray(), ""); if (_type.dataStoredIn(DataLocation::Storage)) solAssert(_type.baseType()->storageBytes() > 16, ""); string functionName = "array_nextElement_" + _type.identifier(); return m_functionCollector->createFunction(functionName, [&]() { Whiskers templ(R"( function (ptr) -> next { next := add(ptr, ) } )"); templ("functionName", functionName); switch (_type.location()) { case DataLocation::Memory: templ("advance", "0x20"); break; case DataLocation::Storage: { u256 size = _type.baseType()->storageSize(); solAssert(size >= 1, ""); templ("advance", toCompactHexWithPrefix(size)); break; } case DataLocation::CallData: { u256 size = _type.baseType()->isDynamicallyEncoded() ? 32 : _type.baseType()->calldataEncodedSize(); solAssert(size >= 32 && size % 32 == 0, ""); templ("advance", toCompactHexWithPrefix(size)); break; } } return templ.render(); }); } string YulUtilFunctions::mappingIndexAccessFunction(MappingType const& _mappingType, Type const& _keyType) { solAssert(_keyType.sizeOnStack() <= 1, ""); string functionName = "mapping_index_access_" + _mappingType.identifier() + "_of_" + _keyType.identifier(); return m_functionCollector->createFunction(functionName, [&]() { if (_mappingType.keyType()->isDynamicallySized()) return Whiskers(R"( function (slot ) -> dataSlot { dataSlot := (slot ) } )") ("functionName", functionName) ("key", _keyType.sizeOnStack() > 0 ? "key" : "") ("comma", _keyType.sizeOnStack() > 0 ? "," : "") ("hash", packedHashFunction( {&_keyType, TypeProvider::uint256()}, {_mappingType.keyType(), TypeProvider::uint256()} )) .render(); else { solAssert(CompilerUtils::freeMemoryPointer >= 0x40, ""); solAssert(!_mappingType.keyType()->isDynamicallyEncoded(), ""); solAssert(_mappingType.keyType()->calldataEncodedSize(false) <= 0x20, ""); Whiskers templ(R"( function (slot ) -> dataSlot { mstore(0, ) mstore(0x20, slot) dataSlot := keccak256(0, 0x40) } )"); templ("functionName", functionName); templ("key", _keyType.sizeOnStack() == 1 ? ", key" : ""); if (_keyType.sizeOnStack() == 0) templ("convertedKey", conversionFunction(_keyType, *_mappingType.keyType()) + "()"); else templ("convertedKey", conversionFunction(_keyType, *_mappingType.keyType()) + "(key)"); return templ.render(); } }); } string YulUtilFunctions::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 (slot) -> value { value := (sload(slot)) } )") ("functionName", functionName) ("extract", extractFromStorageValue(_type, _offset, false)) .render(); }); } string YulUtilFunctions::readFromStorageDynamic(Type const& _type, bool _splitFunctionTypes) { solUnimplementedAssert(!_splitFunctionTypes, ""); string functionName = "read_from_storage_dynamic" + string(_splitFunctionTypes ? "split_" : "") + "_" + _type.identifier(); return m_functionCollector->createFunction(functionName, [&] { solAssert(_type.sizeOnStack() == 1, ""); return Whiskers(R"( function (slot, offset) -> value { value := (sload(slot), offset) } )") ("functionName", functionName) ("extract", extractFromStorageValueDynamic(_type, _splitFunctionTypes)) .render(); }); } string YulUtilFunctions::updateStorageValueFunction(Type const& _type, boost::optional const _offset) { string const functionName = "update_storage_value_" + (_offset.is_initialized() ? ("offset_" + to_string(*_offset)) : "") + _type.identifier(); return m_functionCollector->createFunction(functionName, [&] { if (_type.isValueType()) { solAssert(_type.storageBytes() <= 32, "Invalid storage bytes size."); solAssert(_type.storageBytes() > 0, "Invalid storage bytes size."); return Whiskers(R"( function (slot, value) { sstore(slot, (sload(slot), (value))) } )") ("functionName", functionName) ("update", _offset.is_initialized() ? updateByteSliceFunction(_type.storageBytes(), *_offset) : updateByteSliceFunctionDynamic(_type.storageBytes()) ) ("offset", _offset.is_initialized() ? "" : "offset, ") ("prepare", prepareStoreFunction(_type)) .render(); } else { if (_type.category() == Type::Category::Array) solUnimplementedAssert(false, ""); else if (_type.category() == Type::Category::Struct) solUnimplementedAssert(false, ""); else solAssert(false, "Invalid non-value type for assignment."); } }); } string YulUtilFunctions::extractFromStorageValueDynamic(Type const& _type, bool _splitFunctionTypes) { solUnimplementedAssert(!_splitFunctionTypes, ""); string functionName = "extract_from_storage_value_dynamic" + string(_splitFunctionTypes ? "split_" : "") + _type.identifier(); return m_functionCollector->createFunction(functionName, [&] { return Whiskers(R"( function (slot_value, offset) -> value { value := ((mul(offset, 8), slot_value)) } )") ("functionName", functionName) ("shr", shiftRightFunctionDynamic()) ("cleanupStorage", cleanupFromStorageFunction(_type, _splitFunctionTypes)) .render(); }); } string YulUtilFunctions::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 (slot_value) -> value { value := ((slot_value)) } )") ("functionName", functionName) ("shr", shiftRightFunction(_offset * 8)) ("cleanupStorage", cleanupFromStorageFunction(_type, _splitFunctionTypes)) .render(); }); } string YulUtilFunctions::cleanupFromStorageFunction(Type const& _type, bool _splitFunctionTypes) { solAssert(_type.isValueType(), ""); solUnimplementedAssert(!_splitFunctionTypes, ""); string functionName = string("cleanup_from_storage_") + (_splitFunctionTypes ? "split_" : "") + _type.identifier(); return m_functionCollector->createFunction(functionName, [&] { Whiskers templ(R"( function (value) -> cleaned { cleaned := } )"); templ("functionName", functionName); unsigned storageBytes = _type.storageBytes(); if (IntegerType const* type = dynamic_cast(&_type)) if (type->isSigned() && storageBytes != 32) { templ("cleaned", "signextend(" + to_string(storageBytes - 1) + ", value)"); return templ.render(); } if (storageBytes == 32) templ("cleaned", "value"); else if (_type.leftAligned()) templ("cleaned", shiftLeftFunction(256 - 8 * storageBytes) + "(value)"); else templ("cleaned", "and(value, " + toCompactHexWithPrefix((u256(1) << (8 * storageBytes)) - 1) + ")"); return templ.render(); }); } string YulUtilFunctions::prepareStoreFunction(Type const& _type) { solUnimplementedAssert(_type.category() != Type::Category::Function, ""); string functionName = "prepare_store_" + _type.identifier(); return m_functionCollector->createFunction(functionName, [&]() { Whiskers templ(R"( function (value) -> ret { ret := } )"); templ("functionName", functionName); if (_type.category() == Type::Category::FixedBytes) templ("actualPrepare", shiftRightFunction(256 - 8 * _type.storageBytes()) + "(value)"); else templ("actualPrepare", "value"); return templ.render(); }); } string YulUtilFunctions::allocationFunction() { string functionName = "allocateMemory"; return m_functionCollector->createFunction(functionName, [&]() { return Whiskers(R"( function (size) -> memPtr { memPtr := mload() let newFreePtr := add(memPtr, size) // protect against overflow if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { revert(0, 0) } mstore(, newFreePtr) } )") ("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer)) ("functionName", functionName) .render(); }); } string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to) { if (_from.sizeOnStack() != 1 || _to.sizeOnStack() != 1) return conversionFunctionSpecial(_from, _to); string functionName = "convert_" + _from.identifier() + "_to_" + _to.identifier(); return m_functionCollector->createFunction(functionName, [&]() { Whiskers templ(R"( function (value) -> converted { } )"); templ("functionName", functionName); string body; auto toCategory = _to.category(); auto fromCategory = _from.category(); switch (fromCategory) { case Type::Category::Address: body = Whiskers("converted := (value)") ("convert", conversionFunction(IntegerType(160), _to)) .render(); break; case Type::Category::Integer: case Type::Category::RationalNumber: case Type::Category::Contract: { if (RationalNumberType const* rational = dynamic_cast(&_from)) solUnimplementedAssert(!rational->isFractional(), "Not yet implemented - FixedPointType."); if (toCategory == Type::Category::FixedBytes) { solAssert( fromCategory == Type::Category::Integer || fromCategory == Type::Category::RationalNumber, "Invalid conversion to FixedBytesType requested." ); FixedBytesType const& toBytesType = dynamic_cast(_to); body = Whiskers("converted := ((value))") ("shiftLeft", shiftLeftFunction(256 - toBytesType.numBytes() * 8)) ("clean", cleanupFunction(_from)) .render(); } else if (toCategory == Type::Category::Enum) { solAssert(_from.mobileType(), ""); body = Whiskers("converted := ((value))") ("cleanEnum", cleanupFunction(_to)) // "mobileType()" returns integer type for rational ("cleanInt", cleanupFunction(*_from.mobileType())) .render(); } else if (toCategory == Type::Category::FixedPoint) solUnimplemented("Not yet implemented - FixedPointType."); else if (toCategory == Type::Category::Address) body = Whiskers("converted := (value)") ("convert", conversionFunction(_from, IntegerType(160))) .render(); else { solAssert( toCategory == Type::Category::Integer || toCategory == Type::Category::Contract, ""); IntegerType const addressType(160); IntegerType const& to = toCategory == Type::Category::Integer ? dynamic_cast(_to) : addressType; // Clean according to the "to" type, except if this is // a widening conversion. IntegerType const* cleanupType = &to; if (fromCategory != Type::Category::RationalNumber) { IntegerType const& from = fromCategory == Type::Category::Integer ? dynamic_cast(_from) : addressType; if (to.numBits() > from.numBits()) cleanupType = &from; } body = Whiskers("converted := (value)") ("cleanInt", cleanupFunction(*cleanupType)) .render(); } break; } case Type::Category::Bool: { solAssert(_from == _to, "Invalid conversion for bool."); body = Whiskers("converted := (value)") ("clean", cleanupFunction(_from)) .render(); break; } case Type::Category::FixedPoint: solUnimplemented("Fixed point types not implemented."); break; case Type::Category::Array: solUnimplementedAssert(false, "Array conversion not implemented."); break; case Type::Category::Struct: solUnimplementedAssert(false, "Struct conversion not implemented."); break; case Type::Category::FixedBytes: { FixedBytesType const& from = dynamic_cast(_from); if (toCategory == Type::Category::Integer) body = Whiskers("converted := ((value))") ("shift", shiftRightFunction(256 - from.numBytes() * 8)) ("convert", conversionFunction(IntegerType(from.numBytes() * 8), _to)) .render(); else if (toCategory == Type::Category::Address) body = Whiskers("converted := (value)") ("convert", conversionFunction(_from, IntegerType(160))) .render(); else { // clear for conversion to longer bytes solAssert(toCategory == Type::Category::FixedBytes, "Invalid type conversion requested."); body = Whiskers("converted := (value)") ("clean", cleanupFunction(from)) .render(); } break; } case Type::Category::Function: { solAssert(false, "Conversion should not be called for function types."); break; } case Type::Category::Enum: { solAssert(toCategory == Type::Category::Integer || _from == _to, ""); EnumType const& enumType = dynamic_cast(_from); body = Whiskers("converted := (value)") ("clean", cleanupFunction(enumType)) .render(); break; } case Type::Category::Tuple: { solUnimplementedAssert(false, "Tuple conversion not implemented."); break; } default: solAssert(false, ""); } solAssert(!body.empty(), _from.canonicalName() + " to " + _to.canonicalName()); templ("body", body); return templ.render(); }); } string YulUtilFunctions::cleanupFunction(Type const& _type) { string functionName = string("cleanup_") + _type.identifier(); return m_functionCollector->createFunction(functionName, [&]() { Whiskers templ(R"( function (value) -> cleaned { } )"); templ("functionName", functionName); switch (_type.category()) { case Type::Category::Address: templ("body", "cleaned := " + cleanupFunction(IntegerType(160)) + "(value)"); break; case Type::Category::Integer: { IntegerType const& type = dynamic_cast(_type); if (type.numBits() == 256) templ("body", "cleaned := value"); else if (type.isSigned()) templ("body", "cleaned := signextend(" + to_string(type.numBits() / 8 - 1) + ", value)"); else templ("body", "cleaned := and(value, " + toCompactHexWithPrefix((u256(1) << type.numBits()) - 1) + ")"); break; } case Type::Category::RationalNumber: templ("body", "cleaned := value"); break; case Type::Category::Bool: templ("body", "cleaned := iszero(iszero(value))"); break; case Type::Category::FixedPoint: solUnimplemented("Fixed point types not implemented."); break; case Type::Category::Function: solAssert(dynamic_cast(_type).kind() == FunctionType::Kind::External, ""); templ("body", "cleaned := " + cleanupFunction(FixedBytesType(24)) + "(value)"); break; case Type::Category::Array: case Type::Category::Struct: case Type::Category::Mapping: solAssert(_type.dataStoredIn(DataLocation::Storage), "Cleanup requested for non-storage reference type."); templ("body", "cleaned := value"); break; case Type::Category::FixedBytes: { FixedBytesType const& type = dynamic_cast(_type); if (type.numBytes() == 32) templ("body", "cleaned := value"); else if (type.numBytes() == 0) // This is disallowed in the type system. solAssert(false, ""); else { size_t numBits = type.numBytes() * 8; u256 mask = ((u256(1) << numBits) - 1) << (256 - numBits); templ("body", "cleaned := and(value, " + toCompactHexWithPrefix(mask) + ")"); } break; } case Type::Category::Contract: { AddressType addressType(dynamic_cast(_type).isPayable() ? StateMutability::Payable : StateMutability::NonPayable ); templ("body", "cleaned := " + cleanupFunction(addressType) + "(value)"); break; } case Type::Category::Enum: { // Out of range enums cannot be truncated unambigiously and therefore it should be an error. templ("body", "cleaned := value " + validatorFunction(_type) + "(value)"); break; } case Type::Category::InaccessibleDynamic: templ("body", "cleaned := 0"); break; default: solAssert(false, "Cleanup of type " + _type.identifier() + " requested."); } return templ.render(); }); } string YulUtilFunctions::validatorFunction(Type const& _type, bool _revertOnFailure) { string functionName = string("validator_") + (_revertOnFailure ? "revert_" : "assert_") + _type.identifier(); return m_functionCollector->createFunction(functionName, [&]() { Whiskers templ(R"( function (value) { if iszero() { } } )"); templ("functionName", functionName); if (_revertOnFailure) templ("failure", "revert(0, 0)"); else templ("failure", "invalid()"); switch (_type.category()) { case Type::Category::Address: case Type::Category::Integer: case Type::Category::RationalNumber: case Type::Category::Bool: case Type::Category::FixedPoint: case Type::Category::Function: case Type::Category::Array: case Type::Category::Struct: case Type::Category::Mapping: case Type::Category::FixedBytes: case Type::Category::Contract: { templ("condition", "eq(value, " + cleanupFunction(_type) + "(value))"); break; } case Type::Category::Enum: { size_t members = dynamic_cast(_type).numberOfMembers(); solAssert(members > 0, "empty enum should have caused a parser error."); templ("condition", "lt(value, " + to_string(members) + ")"); break; } case Type::Category::InaccessibleDynamic: templ("condition", "1"); break; default: solAssert(false, "Validation of type " + _type.identifier() + " requested."); } return templ.render(); }); } string YulUtilFunctions::packedHashFunction( vector const& _givenTypes, vector const& _targetTypes ) { string functionName = string("packed_hashed_"); for (auto const& t: _givenTypes) functionName += t->identifier() + "_"; functionName += "_to_"; for (auto const& t: _targetTypes) functionName += t->identifier() + "_"; size_t sizeOnStack = 0; for (Type const* t: _givenTypes) sizeOnStack += t->sizeOnStack(); return m_functionCollector->createFunction(functionName, [&]() { Whiskers templ(R"( function () -> hash { let pos := mload() let end := (pos ) hash := keccak256(pos, sub(end, pos)) } )"); templ("functionName", functionName); templ("variables", suffixedVariableNameList("var_", 1, 1 + sizeOnStack)); templ("comma", sizeOnStack > 0 ? "," : ""); templ("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer)); templ("packedEncode", ABIFunctions(m_evmVersion, m_functionCollector).tupleEncoderPacked(_givenTypes, _targetTypes)); return templ.render(); }); } string YulUtilFunctions::forwardingRevertFunction() { bool forward = m_evmVersion.supportsReturndata(); string functionName = "revert_forward_" + to_string(forward); return m_functionCollector->createFunction(functionName, [&]() { if (forward) return Whiskers(R"( function () { returndatacopy(0, 0, returndatasize()) revert(0, returndatasize()) } )") ("functionName", functionName) .render(); else return Whiskers(R"( function () { revert(0, 0) } )") ("functionName", functionName) .render(); }); } string YulUtilFunctions::suffixedVariableNameList(string const& _baseName, size_t _startSuffix, size_t _endSuffix) { string result; if (_startSuffix < _endSuffix) { result = _baseName + to_string(_startSuffix++); while (_startSuffix < _endSuffix) result += ", " + _baseName + to_string(_startSuffix++); } else if (_endSuffix < _startSuffix) { result = _baseName + to_string(_endSuffix++); while (_endSuffix < _startSuffix) result = _baseName + to_string(_endSuffix++) + ", " + result; } return result; } std::string YulUtilFunctions::decrementCheckedFunction(Type const& _type) { IntegerType const& type = dynamic_cast(_type); string const functionName = "decrement_" + _type.identifier(); return m_functionCollector->createFunction(functionName, [&]() { u256 minintval; // Smallest admissible value to decrement if (type.isSigned()) minintval = 0 - (u256(1) << (type.numBits() - 1)) + 1; else minintval = 1; return Whiskers(R"( function (value) -> ret { if (value, ) { revert(0,0) } ret := sub(value, 1) } )") ("functionName", functionName) ("minval", toCompactHexWithPrefix(minintval)) ("lt", type.isSigned() ? "slt" : "lt") .render(); }); } std::string YulUtilFunctions::incrementCheckedFunction(Type const& _type) { IntegerType const& type = dynamic_cast(_type); string const functionName = "increment_" + _type.identifier(); return m_functionCollector->createFunction(functionName, [&]() { u256 maxintval; // Biggest admissible value to increment if (type.isSigned()) maxintval = (u256(1) << (type.numBits() - 1)) - 2; else maxintval = (u256(1) << type.numBits()) - 2; return Whiskers(R"( function (value) -> ret { if (value, ) { revert(0,0) } ret := add(value, 1) } )") ("functionName", functionName) ("maxval", toCompactHexWithPrefix(maxintval)) ("gt", type.isSigned() ? "sgt" : "gt") .render(); }); } string YulUtilFunctions::negateNumberCheckedFunction(Type const& _type) { IntegerType const& type = dynamic_cast(_type); solAssert(type.isSigned(), "Expected signed type!"); string const functionName = "negate_" + _type.identifier(); u256 const minintval = 0 - (u256(1) << (type.numBits() - 1)) + 1; return m_functionCollector->createFunction(functionName, [&]() { return Whiskers(R"( function (_value) -> ret { if slt(_value, ) { revert(0,0) } ret := sub(0, _value) } )") ("functionName", functionName) ("minval", toCompactHexWithPrefix(minintval)) .render(); }); } string YulUtilFunctions::zeroValueFunction(Type const& _type) { solUnimplementedAssert(_type.sizeOnStack() == 1, "Stacksize not yet implemented!"); solUnimplementedAssert(_type.isValueType(), "Zero value for non-value types not yet implemented"); string const functionName = "zero_value_for_" + _type.identifier(); return m_functionCollector->createFunction(functionName, [&]() { return Whiskers(R"( function () -> ret { } )") ("functionName", functionName) ("body", "ret := 0x0") .render(); }); } string YulUtilFunctions::conversionFunctionSpecial(Type const& _from, Type const& _to) { string functionName = "convert_" + _from.identifier() + "_to_" + _to.identifier(); return m_functionCollector->createFunction(functionName, [&]() { solUnimplementedAssert( _from.category() == Type::Category::StringLiteral, "Type conversion " + _from.toString() + " -> " + _to.toString() + " not yet implemented." ); string const& data = dynamic_cast(_from).value(); if (_to.category() == Type::Category::FixedBytes) { unsigned const numBytes = dynamic_cast(_to).numBytes(); solAssert(data.size() <= 32, ""); Whiskers templ(R"( function () -> converted { converted := } )"); templ("functionName", functionName); templ("data", formatNumber( h256::Arith(h256(data, h256::AlignLeft)) & (~(u256(-1) >> (8 * numBytes))) )); return templ.render(); } else if (_to.category() == Type::Category::Array) { auto const& arrayType = dynamic_cast(_to); solAssert(arrayType.isByteArray(), ""); size_t words = (data.size() + 31) / 32; size_t storageSize = 32 + words * 32; Whiskers templ(R"( function () -> converted { converted := () mstore(converted, ) <#word> mstore(add(converted, ), ) } )"); templ("functionName", functionName); templ("allocate", allocationFunction()); templ("storageSize", to_string(storageSize)); templ("size", to_string(data.size())); vector> wordParams(words); for (size_t i = 0; i < words; ++i) { wordParams[i]["offset"] = to_string(32 + i * 32); wordParams[i]["wordValue"] = "0x" + h256(data.substr(32 * i, 32), h256::AlignLeft).hex(); } templ("word", wordParams); return templ.render(); } else solAssert( false, "Invalid conversion from string literal to " + _to.toString() + " requested." ); }); }