mirror of
				https://github.com/ethereum/solidity
				synced 2023-10-03 13:03:40 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			802 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			802 lines
		
	
	
		
			23 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::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::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::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)
 | |
| {
 | |
| 	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::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;
 | |
| }
 |