mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
1122 lines
33 KiB
C++
1122 lines
33 KiB
C++
/*
|
|
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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
/**
|
|
* Component that can generate various useful Yul functions.
|
|
*/
|
|
|
|
#include <libsolidity/codegen/YulUtilFunctions.h>
|
|
|
|
#include <libsolidity/codegen/MultiUseYulFunctionCollector.h>
|
|
#include <libsolidity/ast/AST.h>
|
|
#include <libsolidity/codegen/CompilerUtils.h>
|
|
#include <libdevcore/Whiskers.h>
|
|
|
|
#include <boost/algorithm/string/join.hpp>
|
|
#include <boost/range/adaptor/reversed.hpp>
|
|
|
|
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 <functionName>(addr, selector) -> combined {
|
|
combined := <shl64>(or(<shl32>(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 <functionName>(combined) -> addr, selector {
|
|
combined := <shr64>(combined)
|
|
selector := and(combined, 0xffffffff)
|
|
addr := <shr32>(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 <functionName>(src, dst, length) {
|
|
calldatacopy(dst, src, length)
|
|
// clear end
|
|
mstore(add(dst, length), 0)
|
|
}
|
|
)")
|
|
("functionName", functionName)
|
|
.render();
|
|
}
|
|
else
|
|
{
|
|
return Whiskers(R"(
|
|
function <functionName>(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 <functionName>(condition) {
|
|
if iszero(condition) { <invalidOrRevert> }
|
|
}
|
|
)")
|
|
("invalidOrRevert", _assert ? "invalid()" : "revert(0, 0)")
|
|
("functionName", functionName)
|
|
.render();
|
|
|
|
|
|
solUnimplemented("require() with two parameters is not yet implemented.");
|
|
// TODO The code below is completely untested as we don't support StringLiterals yet
|
|
int const hashHeaderSize = 4;
|
|
int const byteSize = 8;
|
|
u256 const errorHash =
|
|
u256(FixedHash<hashHeaderSize>::Arith(
|
|
FixedHash<hashHeaderSize>(dev::keccak256("Error(string)"))
|
|
)) << (256 - hashHeaderSize * byteSize);
|
|
|
|
string const encodeFunc = ABIFunctions(m_evmVersion, m_functionCollector)
|
|
.tupleEncoder(
|
|
{_messageType},
|
|
{TypeProvider::stringMemory()}
|
|
);
|
|
|
|
return Whiskers(R"(
|
|
function <functionName>(condition, message) {
|
|
if iszero(condition) {
|
|
let fmp := mload(<freeMemPointer>)
|
|
mstore(fmp, <errorHash>)
|
|
let end := <abiEncodeFunc>(add(fmp, <hashHeaderSize>), message)
|
|
revert(fmp, sub(end, fmp))
|
|
}
|
|
}
|
|
)")
|
|
("functionName", functionName)
|
|
("freeMemPointer", to_string(CompilerUtils::freeMemoryPointer))
|
|
("errorHash", errorHash.str())
|
|
("abiEncodeFunc", encodeFunc)
|
|
("hashHeaderSize", to_string(hashHeaderSize))
|
|
.render();
|
|
});
|
|
}
|
|
|
|
string YulUtilFunctions::leftAlignFunction(Type const& _type)
|
|
{
|
|
string functionName = string("leftAlign_") + _type.identifier();
|
|
return m_functionCollector->createFunction(functionName, [&]() {
|
|
Whiskers templ(R"(
|
|
function <functionName>(value) -> aligned {
|
|
<body>
|
|
}
|
|
)");
|
|
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<IntegerType const&>(_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<EnumType const&>(_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);
|
|
if (m_evmVersion.hasBitwiseShifting())
|
|
{
|
|
return m_functionCollector->createFunction(functionName, [&]() {
|
|
return
|
|
Whiskers(R"(
|
|
function <functionName>(value) -> newValue {
|
|
newValue := shl(<numBits>, value)
|
|
}
|
|
)")
|
|
("functionName", functionName)
|
|
("numBits", to_string(_numBits))
|
|
.render();
|
|
});
|
|
}
|
|
else
|
|
{
|
|
return m_functionCollector->createFunction(functionName, [&]() {
|
|
return
|
|
Whiskers(R"(
|
|
function <functionName>(value) -> newValue {
|
|
newValue := mul(value, <multiplier>)
|
|
}
|
|
)")
|
|
("functionName", functionName)
|
|
("multiplier", toCompactHexWithPrefix(u256(1) << _numBits))
|
|
.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";
|
|
if (m_evmVersion.hasBitwiseShifting())
|
|
{
|
|
return m_functionCollector->createFunction(functionName, [&]() {
|
|
return
|
|
Whiskers(R"(
|
|
function <functionName>(value) -> newValue {
|
|
newValue := shr(<numBits>, value)
|
|
}
|
|
)")
|
|
("functionName", functionName)
|
|
("numBits", to_string(_numBits))
|
|
.render();
|
|
});
|
|
}
|
|
else
|
|
{
|
|
return m_functionCollector->createFunction(functionName, [&]() {
|
|
return
|
|
Whiskers(R"(
|
|
function <functionName>(value) -> newValue {
|
|
newValue := div(value, <multiplier>)
|
|
}
|
|
)")
|
|
("functionName", functionName)
|
|
("multiplier", toCompactHexWithPrefix(u256(1) << _numBits))
|
|
.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 <functionName>(value, toInsert) -> result {
|
|
let mask := <mask>
|
|
toInsert := <shl>(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::roundUpFunction()
|
|
{
|
|
string functionName = "round_up_to_mul_of_32";
|
|
return m_functionCollector->createFunction(functionName, [&]() {
|
|
return
|
|
Whiskers(R"(
|
|
function <functionName>(value) -> result {
|
|
result := and(add(value, 31), not(31))
|
|
}
|
|
)")
|
|
("functionName", functionName)
|
|
.render();
|
|
});
|
|
}
|
|
|
|
string YulUtilFunctions::overflowCheckedUIntAddFunction(size_t _bits)
|
|
{
|
|
solAssert(0 < _bits && _bits <= 256 && _bits % 8 == 0, "");
|
|
string functionName = "checked_add_uint_" + to_string(_bits);
|
|
return m_functionCollector->createFunction(functionName, [&]() {
|
|
if (_bits < 256)
|
|
return
|
|
Whiskers(R"(
|
|
function <functionName>(x, y) -> sum {
|
|
let mask := <mask>
|
|
sum := add(and(x, mask), and(y, mask))
|
|
if and(sum, not(mask)) { revert(0, 0) }
|
|
}
|
|
)")
|
|
("functionName", functionName)
|
|
("mask", toCompactHexWithPrefix((u256(1) << _bits) - 1))
|
|
.render();
|
|
else
|
|
return
|
|
Whiskers(R"(
|
|
function <functionName>(x, y) -> sum {
|
|
sum := add(x, y)
|
|
if lt(sum, x) { revert(0, 0) }
|
|
}
|
|
)")
|
|
("functionName", functionName)
|
|
.render();
|
|
});
|
|
}
|
|
|
|
string YulUtilFunctions::arrayLengthFunction(ArrayType const& _type)
|
|
{
|
|
string functionName = "array_length_" + _type.identifier();
|
|
return m_functionCollector->createFunction(functionName, [&]() {
|
|
Whiskers w(R"(
|
|
function <functionName>(value) -> length {
|
|
<body>
|
|
}
|
|
)");
|
|
w("functionName", functionName);
|
|
string body;
|
|
if (!_type.isDynamicallySized())
|
|
body = "length := " + toCompactHexWithPrefix(_type.length());
|
|
else
|
|
{
|
|
switch (_type.location())
|
|
{
|
|
case DataLocation::CallData:
|
|
solAssert(false, "called regular array length function on calldata array");
|
|
break;
|
|
case DataLocation::Memory:
|
|
body = "length := mload(value)";
|
|
break;
|
|
case DataLocation::Storage:
|
|
if (_type.isByteArray())
|
|
{
|
|
// 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.
|
|
body = R"(
|
|
length := sload(value)
|
|
let mask := sub(mul(0x100, iszero(and(length, 1))), 1)
|
|
length := div(and(length, mask), 2)
|
|
)";
|
|
}
|
|
else
|
|
body = "length := sload(value)";
|
|
break;
|
|
}
|
|
}
|
|
solAssert(!body.empty(), "");
|
|
w("body", body);
|
|
return w.render();
|
|
});
|
|
}
|
|
|
|
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 <functionName>(length) -> size {
|
|
// Make sure we can allocate memory without overflow
|
|
if gt(length, 0xffffffffffffffff) { revert(0, 0) }
|
|
size := <allocationSize>
|
|
<addLengthSlot>
|
|
}
|
|
)");
|
|
w("functionName", functionName);
|
|
if (_type.isByteArray())
|
|
// Round up
|
|
w("allocationSize", "and(add(length, 0x1f), not(0x1f))");
|
|
else
|
|
w("allocationSize", "mul(length, 0x20)");
|
|
if (_type.isDynamicallySized())
|
|
w("addLengthSlot", "size := add(size, 0x20)");
|
|
else
|
|
w("addLengthSlot", "");
|
|
return w.render();
|
|
});
|
|
}
|
|
|
|
string YulUtilFunctions::arrayDataAreaFunction(ArrayType const& _type)
|
|
{
|
|
string functionName = "array_dataslot_" + _type.identifier();
|
|
return m_functionCollector->createFunction(functionName, [&]() {
|
|
switch (_type.location())
|
|
{
|
|
case DataLocation::Memory:
|
|
if (_type.isDynamicallySized())
|
|
return Whiskers(R"(
|
|
function <functionName>(memPtr) -> dataPtr {
|
|
dataPtr := add(memPtr, 0x20)
|
|
}
|
|
)")
|
|
("functionName", functionName)
|
|
.render();
|
|
else
|
|
return Whiskers(R"(
|
|
function <functionName>(memPtr) -> dataPtr {
|
|
dataPtr := memPtr
|
|
}
|
|
)")
|
|
("functionName", functionName)
|
|
.render();
|
|
case DataLocation::Storage:
|
|
if (_type.isDynamicallySized())
|
|
{
|
|
Whiskers w(R"(
|
|
function <functionName>(slot) -> dataSlot {
|
|
mstore(0, slot)
|
|
dataSlot := keccak256(0, 0x20)
|
|
}
|
|
)");
|
|
w("functionName", functionName);
|
|
return w.render();
|
|
}
|
|
else
|
|
{
|
|
Whiskers w(R"(
|
|
function <functionName>(slot) -> dataSlot {
|
|
dataSlot := slot
|
|
}
|
|
)");
|
|
w("functionName", functionName);
|
|
return w.render();
|
|
}
|
|
case DataLocation::CallData:
|
|
{
|
|
// Calldata arrays 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.
|
|
Whiskers w(R"(
|
|
function <functionName>(slot) -> dataSlot {
|
|
dataSlot := slot
|
|
}
|
|
)");
|
|
w("functionName", functionName);
|
|
return w.render();
|
|
}
|
|
default:
|
|
solAssert(false, "");
|
|
}
|
|
});
|
|
}
|
|
|
|
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, [&]() {
|
|
switch (_type.location())
|
|
{
|
|
case DataLocation::Memory:
|
|
return Whiskers(R"(
|
|
function <functionName>(memPtr) -> nextPtr {
|
|
nextPtr := add(memPtr, 0x20)
|
|
}
|
|
)")
|
|
("functionName", functionName)
|
|
.render();
|
|
case DataLocation::Storage:
|
|
return Whiskers(R"(
|
|
function <functionName>(slot) -> nextSlot {
|
|
nextSlot := add(slot, 1)
|
|
}
|
|
)")
|
|
("functionName", functionName)
|
|
.render();
|
|
case DataLocation::CallData:
|
|
return Whiskers(R"(
|
|
function <functionName>(calldataPtr) -> nextPtr {
|
|
nextPtr := add(calldataPtr, <stride>)
|
|
}
|
|
)")
|
|
("stride", toCompactHexWithPrefix(_type.baseType()->isDynamicallyEncoded() ? 32 : _type.baseType()->calldataEncodedSize()))
|
|
("functionName", functionName)
|
|
.render();
|
|
default:
|
|
solAssert(false, "");
|
|
}
|
|
});
|
|
}
|
|
|
|
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 <functionName>(slot <comma> <key>) -> dataSlot {
|
|
dataSlot := <hash>(slot <comma> <key>)
|
|
}
|
|
)")
|
|
("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 <functionName>(slot <key>) -> dataSlot {
|
|
mstore(0, <convertedKey>)
|
|
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 <functionName>(slot) -> value {
|
|
value := <extract>(sload(slot))
|
|
}
|
|
)")
|
|
("functionName", functionName)
|
|
("extract", extractFromStorageValue(_type, _offset, false))
|
|
.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 <functionName>(slot_value) -> value {
|
|
value := <cleanupStorage>(<shr>(slot_value))
|
|
}
|
|
)")
|
|
("functionName", functionName)
|
|
("shr", shiftRightFunction(_offset * 8))
|
|
("cleanupStorage", cleanupFromStorageFunction(_type, false))
|
|
.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 <functionName>(value) -> cleaned {
|
|
<body>
|
|
}
|
|
)");
|
|
templ("functionName", functionName);
|
|
|
|
unsigned storageBytes = _type.storageBytes();
|
|
if (IntegerType const* type = dynamic_cast<IntegerType const*>(&_type))
|
|
if (type->isSigned() && storageBytes != 32)
|
|
{
|
|
templ("body", "cleaned := signextend(" + to_string(storageBytes - 1) + ", value)");
|
|
return templ.render();
|
|
}
|
|
|
|
if (storageBytes == 32)
|
|
templ("body", "cleaned := value");
|
|
else if (_type.leftAligned())
|
|
templ("body", "cleaned := " + shiftLeftFunction(256 - 8 * storageBytes) + "(value)");
|
|
else
|
|
templ("body", "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 <functionName>(value) -> ret {
|
|
ret := <actualPrepare>
|
|
}
|
|
)");
|
|
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 <functionName>(size) -> memPtr {
|
|
memPtr := mload(<freeMemoryPointer>)
|
|
let newFreePtr := add(memPtr, size)
|
|
// protect against overflow
|
|
if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { revert(0, 0) }
|
|
mstore(<freeMemoryPointer>, 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 <functionName>(value) -> converted {
|
|
<body>
|
|
}
|
|
)");
|
|
templ("functionName", functionName);
|
|
string body;
|
|
auto toCategory = _to.category();
|
|
auto fromCategory = _from.category();
|
|
switch (fromCategory)
|
|
{
|
|
case Type::Category::Address:
|
|
body =
|
|
Whiskers("converted := <convert>(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<RationalNumberType const*>(&_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<FixedBytesType const&>(_to);
|
|
body =
|
|
Whiskers("converted := <shiftLeft>(<clean>(value))")
|
|
("shiftLeft", shiftLeftFunction(256 - toBytesType.numBytes() * 8))
|
|
("clean", cleanupFunction(_from))
|
|
.render();
|
|
}
|
|
else if (toCategory == Type::Category::Enum)
|
|
{
|
|
solAssert(_from.mobileType(), "");
|
|
body =
|
|
Whiskers("converted := <cleanEnum>(<cleanInt>(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 := <convert>(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<IntegerType const&>(_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<IntegerType const&>(_from) :
|
|
addressType;
|
|
if (to.numBits() > from.numBits())
|
|
cleanupType = &from;
|
|
}
|
|
body =
|
|
Whiskers("converted := <cleanInt>(value)")
|
|
("cleanInt", cleanupFunction(*cleanupType))
|
|
.render();
|
|
}
|
|
break;
|
|
}
|
|
case Type::Category::Bool:
|
|
{
|
|
solAssert(_from == _to, "Invalid conversion for bool.");
|
|
body =
|
|
Whiskers("converted := <clean>(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<FixedBytesType const&>(_from);
|
|
if (toCategory == Type::Category::Integer)
|
|
body =
|
|
Whiskers("converted := <convert>(<shift>(value))")
|
|
("shift", shiftRightFunction(256 - from.numBytes() * 8))
|
|
("convert", conversionFunction(IntegerType(from.numBytes() * 8), _to))
|
|
.render();
|
|
else if (toCategory == Type::Category::Address)
|
|
body =
|
|
Whiskers("converted := <convert>(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 := <clean>(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<decltype(enumType)>(_from);
|
|
body =
|
|
Whiskers("converted := <clean>(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 <functionName>(value) -> cleaned {
|
|
<body>
|
|
}
|
|
)");
|
|
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<IntegerType const&>(_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<FunctionType const&>(_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<FixedBytesType const&>(_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<ContractType const&>(_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 <functionName>(value) {
|
|
if iszero(<condition>) { <failure> }
|
|
}
|
|
)");
|
|
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<EnumType const&>(_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<Type const*> const& _givenTypes,
|
|
vector<Type const*> 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 <functionName>(<variables>) -> hash {
|
|
let pos := mload(<freeMemoryPointer>)
|
|
let end := <packedEncode>(pos <comma> <variables>)
|
|
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::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;
|
|
}
|
|
|
|
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<StringLiteralType const&>(_from).value();
|
|
if (_to.category() == Type::Category::FixedBytes)
|
|
{
|
|
unsigned const numBytes = dynamic_cast<FixedBytesType const&>(_to).numBytes();
|
|
solAssert(data.size() <= 32, "");
|
|
Whiskers templ(R"(
|
|
function <functionName>() -> converted {
|
|
converted := <data>
|
|
}
|
|
)");
|
|
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<ArrayType const&>(_to);
|
|
solAssert(arrayType.isByteArray(), "");
|
|
size_t words = (data.size() + 31) / 32;
|
|
size_t storageSize = 32 + words * 32;
|
|
|
|
Whiskers templ(R"(
|
|
function <functionName>() -> converted {
|
|
converted := <allocate>(<storageSize>)
|
|
mstore(converted, <size>)
|
|
<#word>
|
|
mstore(add(converted, <offset>), <wordValue>)
|
|
</word>
|
|
}
|
|
)");
|
|
templ("functionName", functionName);
|
|
templ("allocate", allocationFunction());
|
|
templ("storageSize", to_string(storageSize));
|
|
templ("size", to_string(data.size()));
|
|
vector<map<string, string>> 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."
|
|
);
|
|
});
|
|
}
|