/*
	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 .
*/
/**
 * @author Christian 
 * @date 2017
 * Routines that generate Yul code related to ABI encoding, decoding and type conversions.
 */
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
using namespace dev;
using namespace dev::solidity;
string ABIFunctions::tupleEncoder(
	TypePointers const& _givenTypes,
	TypePointers const& _targetTypes,
	bool _encodeAsLibraryTypes
)
{
	EncodingOptions options;
	options.encodeAsLibraryTypes = _encodeAsLibraryTypes;
	options.encodeFunctionFromStack = true;
	options.padded = true;
	options.dynamicInplace = false;
	string functionName = string("abi_encode_tuple_");
	for (auto const& t: _givenTypes)
		functionName += t->identifier() + "_";
	functionName += "_to_";
	for (auto const& t: _targetTypes)
		functionName += t->identifier() + "_";
	functionName += options.toFunctionNameSuffix();
	return createExternallyUsedFunction(functionName, [&]() {
		solAssert(!_givenTypes.empty(), "");
		// Note that the values are in reverse due to the difference in calling semantics.
		Whiskers templ(R"(
			function (headStart ) -> tail {
				tail := add(headStart, )
				
			}
		)");
		templ("functionName", functionName);
		size_t const headSize_ = headSize(_targetTypes);
		templ("headSize", to_string(headSize_));
		string valueParams;
		string encodeElements;
		size_t headPos = 0;
		size_t stackPos = 0;
		for (size_t i = 0; i < _givenTypes.size(); ++i)
		{
			solAssert(_givenTypes[i], "");
			solAssert(_targetTypes[i], "");
			size_t sizeOnStack = _givenTypes[i]->sizeOnStack();
			string valueNames = "";
			for (size_t j = 0; j < sizeOnStack; j++)
			{
				valueNames += "value" + to_string(stackPos) + ", ";
				valueParams = ", value" + to_string(stackPos) + valueParams;
				stackPos++;
			}
			bool dynamic = _targetTypes[i]->isDynamicallyEncoded();
			Whiskers elementTempl(
				dynamic ?
				string(R"(
					mstore(add(headStart, ), sub(tail, headStart))
					tail := ( tail)
				)") :
				string(R"(
					( add(headStart, ))
				)")
			);
			elementTempl("values", valueNames);
			elementTempl("pos", to_string(headPos));
			elementTempl("abiEncode", abiEncodingFunction(*_givenTypes[i], *_targetTypes[i], options));
			encodeElements += elementTempl.render();
			headPos += dynamic ? 0x20 : _targetTypes[i]->calldataEncodedSize();
		}
		solAssert(headPos == headSize_, "");
		templ("valueParams", valueParams);
		templ("encodeElements", encodeElements);
		return templ.render();
	});
}
string ABIFunctions::tupleEncoderPacked(
	TypePointers const& _givenTypes,
	TypePointers const& _targetTypes
)
{
	EncodingOptions options;
	options.encodeAsLibraryTypes = false;
	options.encodeFunctionFromStack = true;
	options.padded = false;
	options.dynamicInplace = true;
	string functionName = string("abi_encode_tuple_packed_");
	for (auto const& t: _givenTypes)
		functionName += t->identifier() + "_";
	functionName += "_to_";
	for (auto const& t: _targetTypes)
		functionName += t->identifier() + "_";
	functionName += options.toFunctionNameSuffix();
	return createExternallyUsedFunction(functionName, [&]() {
		solAssert(!_givenTypes.empty(), "");
		// Note that the values are in reverse due to the difference in calling semantics.
		Whiskers templ(R"(
			function (pos ) -> end {
				
				end := pos
			}
		)");
		templ("functionName", functionName);
		string valueParams;
		string encodeElements;
		size_t stackPos = 0;
		for (size_t i = 0; i < _givenTypes.size(); ++i)
		{
			solAssert(_givenTypes[i], "");
			solAssert(_targetTypes[i], "");
			size_t sizeOnStack = _givenTypes[i]->sizeOnStack();
			string valueNames = "";
			for (size_t j = 0; j < sizeOnStack; j++)
			{
				valueNames += "value" + to_string(stackPos) + ", ";
				valueParams = ", value" + to_string(stackPos) + valueParams;
				stackPos++;
			}
			bool dynamic = _targetTypes[i]->isDynamicallyEncoded();
			Whiskers elementTempl(
				dynamic ?
				string(R"(
					pos := ( pos)
				)") :
				string(R"(
					( pos)
					pos := add(pos, )
				)")
			);
			elementTempl("values", valueNames);
			if (!dynamic)
				elementTempl("calldataEncodedSize", to_string(_targetTypes[i]->calldataEncodedSize(false)));
			elementTempl("abiEncode", abiEncodingFunction(*_givenTypes[i], *_targetTypes[i], options));
			encodeElements += elementTempl.render();
		}
		templ("valueParams", valueParams);
		templ("encodeElements", encodeElements);
		return templ.render();
	});
}
string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory)
{
	string functionName = string("abi_decode_tuple_");
	for (auto const& t: _types)
		functionName += t->identifier();
	if (_fromMemory)
		functionName += "_fromMemory";
	solAssert(!_types.empty(), "");
	return createExternallyUsedFunction(functionName, [&]() {
		TypePointers decodingTypes;
		for (auto const& t: _types)
			decodingTypes.emplace_back(t->decodingType());
		Whiskers templ(R"(
			function (headStart, dataEnd) ->  {
				if slt(sub(dataEnd, headStart), ) { revert(0, 0) }
				
			}
		)");
		templ("functionName", functionName);
		templ("minimumSize", to_string(headSize(decodingTypes)));
		string decodeElements;
		vector valueReturnParams;
		size_t headPos = 0;
		size_t stackPos = 0;
		for (size_t i = 0; i < _types.size(); ++i)
		{
			solAssert(_types[i], "");
			solAssert(decodingTypes[i], "");
			size_t sizeOnStack = _types[i]->sizeOnStack();
			solAssert(sizeOnStack == decodingTypes[i]->sizeOnStack(), "");
			solAssert(sizeOnStack > 0, "");
			vector valueNamesLocal;
			for (size_t j = 0; j < sizeOnStack; j++)
			{
				valueNamesLocal.emplace_back("value" + to_string(stackPos));
				valueReturnParams.emplace_back("value" + to_string(stackPos));
				stackPos++;
			}
			bool dynamic = decodingTypes[i]->isDynamicallyEncoded();
			Whiskers elementTempl(
				dynamic ?
				R"(
				{
					let offset := (add(headStart, ))
					if gt(offset, 0xffffffffffffffff) { revert(0, 0) }
					 := (add(headStart, offset), dataEnd)
				}
				)" :
				R"(
				{
					let offset := 
					 := (add(headStart, offset), dataEnd)
				}
				)"
			);
			elementTempl("load", _fromMemory ? "mload" : "calldataload");
			elementTempl("values", boost::algorithm::join(valueNamesLocal, ", "));
			elementTempl("pos", to_string(headPos));
			elementTempl("abiDecode", abiDecodingFunction(*_types[i], _fromMemory, true));
			decodeElements += elementTempl.render();
			headPos += dynamic ? 0x20 : decodingTypes[i]->calldataEncodedSize();
		}
		templ("valueReturnParams", boost::algorithm::join(valueReturnParams, ", "));
		templ("decodeElements", decodeElements);
		return templ.render();
	});
}
pair> ABIFunctions::requestedFunctions()
{
	string result;
	for (auto const& f: m_requestedFunctions)
		result += f.second;
	m_requestedFunctions.clear();
	return make_pair(result, std::move(m_externallyUsedFunctions));
}
string ABIFunctions::EncodingOptions::toFunctionNameSuffix() const
{
	string suffix;
	if (!padded)
		suffix += "_nonPadded";
	if (dynamicInplace)
		suffix += "_inplace";
	if (encodeFunctionFromStack)
		suffix += "_fromStack";
	if (encodeAsLibraryTypes)
		suffix += "_library";
	return suffix;
}
string ABIFunctions::cleanupFunction(Type const& _type, bool _revertOnFailure)
{
	string functionName = string("cleanup_") + (_revertOnFailure ? "revert_" : "assert_") + _type.identifier();
	return createFunction(functionName, [&]() {
		Whiskers templ(R"(
			function (value) -> cleaned {
				
			}
		)");
		templ("functionName", functionName);
		switch (_type.category())
		{
		case Type::Category::Address:
			templ("body", "cleaned := " + cleanupFunction(IntegerType(160), _revertOnFailure) + "(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::Array:
		case Type::Category::Struct:
			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, _revertOnFailure) + "(value)");
			break;
		}
		case Type::Category::Enum:
		{
			size_t members = dynamic_cast(_type).numberOfMembers();
			solAssert(members > 0, "empty enum should have caused a parser error.");
			Whiskers w("if iszero(lt(value, )) {  } cleaned := value");
			w("members", to_string(members));
			if (_revertOnFailure)
				w("failure", "revert(0, 0)");
			else
				w("failure", "invalid()");
			templ("body", w.render());
			break;
		}
		case Type::Category::InaccessibleDynamic:
			templ("body", "cleaned := 0");
			break;
		default:
			solAssert(false, "Cleanup of type " + _type.identifier() + " requested.");
		}
		return templ.render();
	});
}
string ABIFunctions::conversionFunction(Type const& _from, Type const& _to)
{
	string functionName =
		"convert_" +
		_from.identifier() +
		"_to_" +
		_to.identifier();
	return 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 ABIFunctions::cleanupCombinedExternalFunctionIdFunction()
{
	string functionName = "cleanup_combined_external_function_id";
	return createFunction(functionName, [&]() {
		return Whiskers(R"(
			function (addr_and_selector) -> cleaned {
				cleaned := (addr_and_selector)
			}
		)")
		("functionName", functionName)
		("clean", cleanupFunction(FixedBytesType(24)))
		.render();
	});
}
string ABIFunctions::combineExternalFunctionIdFunction()
{
	string functionName = "combine_external_function_id";
	return 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 ABIFunctions::splitExternalFunctionIdFunction()
{
	string functionName = "split_external_function_id";
	return 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 ABIFunctions::abiEncodingFunction(
	Type const& _from,
	Type const& _to,
	EncodingOptions const& _options
)
{
	TypePointer toInterface = _to.fullEncodingType(_options.encodeAsLibraryTypes, true, false);
	solUnimplementedAssert(toInterface, "Encoding type \"" + _to.toString() + "\" not yet implemented.");
	Type const& to = *toInterface;
	if (_from.category() == Type::Category::StringLiteral)
		return abiEncodingFunctionStringLiteral(_from, to, _options);
	else if (auto toArray = dynamic_cast(&to))
	{
		solAssert(_from.category() == Type::Category::Array, "");
		solAssert(to.dataStoredIn(DataLocation::Memory), "");
		ArrayType const& fromArray = dynamic_cast(_from);
		if (fromArray.location() == DataLocation::CallData)
			return abiEncodingFunctionCalldataArray(fromArray, *toArray, _options);
		else if (!fromArray.isByteArray() && (
				fromArray.location() == DataLocation::Memory ||
				fromArray.baseType()->storageBytes() > 16
		))
			return abiEncodingFunctionSimpleArray(fromArray, *toArray, _options);
		else if (fromArray.location() == DataLocation::Memory)
			return abiEncodingFunctionMemoryByteArray(fromArray, *toArray, _options);
		else if (fromArray.location() == DataLocation::Storage)
			return abiEncodingFunctionCompactStorageArray(fromArray, *toArray, _options);
		else
			solAssert(false, "");
	}
	else if (auto const* toStruct = dynamic_cast(&to))
	{
		StructType const* fromStruct = dynamic_cast(&_from);
		solAssert(fromStruct, "");
		return abiEncodingFunctionStruct(*fromStruct, *toStruct, _options);
	}
	else if (_from.category() == Type::Category::Function)
		return abiEncodingFunctionFunctionType(
			dynamic_cast(_from),
			to,
			_options
		);
	solAssert(_from.sizeOnStack() == 1, "");
	solAssert(to.isValueType(), "");
	solAssert(to.calldataEncodedSize() == 32, "");
	string functionName =
		"abi_encode_" +
		_from.identifier() +
		"_to_" +
		to.identifier() +
		_options.toFunctionNameSuffix();
	return createFunction(functionName, [&]() {
		solAssert(!to.isDynamicallyEncoded(), "");
		Whiskers templ(R"(
			function (value, pos) {
				mstore(pos, )
			}
		)");
		templ("functionName", functionName);
		if (_from.dataStoredIn(DataLocation::Storage))
		{
			// special case: convert storage reference type to value type - this is only
			// possible for library calls where we just forward the storage reference
			solAssert(_options.encodeAsLibraryTypes, "");
			solAssert(_options.padded && !_options.dynamicInplace, "Non-padded / inplace encoding for library call requested.");
			solAssert(to == IntegerType::uint256(), "");
			templ("cleanupConvert", "value");
		}
		else
		{
			string cleanupConvert;
			if (_from == to)
				cleanupConvert = cleanupFunction(_from) + "(value)";
			else
				cleanupConvert = conversionFunction(_from, to) + "(value)";
			if (!_options.padded)
				cleanupConvert = leftAlignFunction(to) + "(" + cleanupConvert + ")";
			templ("cleanupConvert", cleanupConvert);
		}
		return templ.render();
	});
}
string ABIFunctions::abiEncodeAndReturnUpdatedPosFunction(
	Type const& _givenType,
	Type const& _targetType,
	ABIFunctions::EncodingOptions const& _options
)
{
	string functionName =
		"abi_encodeUpdatedPos_" +
		_givenType.identifier() +
		"_to_" +
		_targetType.identifier() +
		_options.toFunctionNameSuffix();
	return createFunction(functionName, [&]() {
		string encoder = abiEncodingFunction(_givenType, _targetType, _options);
		if (_targetType.isDynamicallyEncoded())
			return Whiskers(R"(
				function (value, pos) -> updatedPos {
					updatedPos := (value, pos)
				}
			)")
			("functionName", functionName)
			("encode", encoder)
			.render();
		else
		{
			unsigned encodedSize = _targetType.calldataEncodedSize(_options.padded);
			solAssert(encodedSize != 0, "Invalid encoded size.");
			return Whiskers(R"(
				function (value, pos) -> updatedPos {
					(value, pos)
					updatedPos := add(pos, )
				}
			)")
			("functionName", functionName)
			("encode", encoder)
			("encodedSize", toCompactHexWithPrefix(encodedSize))
			.render();
		}
	});
}
string ABIFunctions::abiEncodingFunctionCalldataArray(
	Type const& _from,
	Type const& _to,
	EncodingOptions const& _options
)
{
	solAssert(_to.isDynamicallySized(), "");
	solAssert(_from.category() == Type::Category::Array, "Unknown dynamic type.");
	solAssert(_to.category() == Type::Category::Array, "Unknown dynamic type.");
	auto const& fromArrayType = dynamic_cast(_from);
	auto const& toArrayType = dynamic_cast(_to);
	solAssert(fromArrayType.location() == DataLocation::CallData, "");
	solAssert(
		*fromArrayType.copyForLocation(DataLocation::Memory, true) ==
		*toArrayType.copyForLocation(DataLocation::Memory, true),
		""
	);
	string functionName =
		"abi_encode_" +
		_from.identifier() +
		"_to_" +
		_to.identifier() +
		_options.toFunctionNameSuffix();
	return createFunction(functionName, [&]() {
		solUnimplementedAssert(fromArrayType.isByteArray(), "Only byte arrays can be encoded from calldata currently.");
		// TODO if this is not a byte array, we might just copy byte-by-byte anyway,
		// because the encoding is position-independent, but we have to check that.
		Whiskers templ(R"(
			//  -> 
			function (start, length, pos) -> end {
				pos := (pos, length)
				(start, pos, length)
				end := add(pos, )
			}
		)");
		templ("storeLength", arrayStoreLengthForEncodingFunction(toArrayType, _options));
		templ("functionName", functionName);
		templ("readableTypeNameFrom", _from.toString(true));
		templ("readableTypeNameTo", _to.toString(true));
		templ("copyFun", copyToMemoryFunction(true));
		templ("lengthPadded", _options.padded ? roundUpFunction() + "(length)" : "length");
		return templ.render();
	});
}
string ABIFunctions::abiEncodingFunctionSimpleArray(
	ArrayType const& _from,
	ArrayType const& _to,
	EncodingOptions const& _options
)
{
	string functionName =
		"abi_encode_" +
		_from.identifier() +
		"_to_" +
		_to.identifier() +
		_options.toFunctionNameSuffix();
	solAssert(_from.isDynamicallySized() == _to.isDynamicallySized(), "");
	solAssert(_from.length() == _to.length(), "");
	solAssert(_from.dataStoredIn(DataLocation::Memory) || _from.dataStoredIn(DataLocation::Storage), "");
	solAssert(!_from.isByteArray(), "");
	solAssert(_from.dataStoredIn(DataLocation::Memory) || _from.baseType()->storageBytes() > 16, "");
	return createFunction(functionName, [&]() {
		bool dynamic = _to.isDynamicallyEncoded();
		bool dynamicBase = _to.baseType()->isDynamicallyEncoded();
		bool inMemory = _from.dataStoredIn(DataLocation::Memory);
		bool const usesTail = dynamicBase && !_options.dynamicInplace;
		Whiskers templ(
			usesTail ?
			R"(
				//  -> 
				function (value, pos)  {
					let length := (value)
					pos := (pos, length)
					let headStart := pos
					let tail := add(pos, mul(length, 0x20))
					let srcPtr := (value)
					for { let i := 0 } lt(i, length) { i := add(i, 1) }
					{
						mstore(pos, sub(tail, headStart))
						tail := (, tail)
						srcPtr := (srcPtr)
						pos := add(pos, 0x20)
					}
					pos := tail
					
				}
			)" :
			R"(
				//  -> 
				function (value, pos)  {
					let length := (value)
					pos := (pos, length)
					let srcPtr := (value)
					for { let i := 0 } lt(i, length) { i := add(i, 1) }
					{
						pos := (, pos)
						srcPtr := (srcPtr)
					}
					
				}
			)"
		);
		templ("functionName", functionName);
		templ("readableTypeNameFrom", _from.toString(true));
		templ("readableTypeNameTo", _to.toString(true));
		templ("return", dynamic ? " -> end " : "");
		templ("assignEnd", dynamic ? "end := pos" : "");
		templ("lengthFun", arrayLengthFunction(_from));
		templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options));
		templ("dataAreaFun", arrayDataAreaFunction(_from));
		EncodingOptions subOptions(_options);
		subOptions.encodeFunctionFromStack = false;
		subOptions.padded = true;
		templ("encodeToMemoryFun", abiEncodeAndReturnUpdatedPosFunction(*_from.baseType(), *_to.baseType(), subOptions));
		templ("arrayElementAccess", inMemory ? "mload(srcPtr)" : _from.baseType()->isValueType() ? "sload(srcPtr)" : "srcPtr" );
		templ("nextArrayElement", nextArrayElementFunction(_from));
		return templ.render();
	});
}
string ABIFunctions::abiEncodingFunctionMemoryByteArray(
	ArrayType const& _from,
	ArrayType const& _to,
	EncodingOptions const& _options
)
{
	string functionName =
		"abi_encode_" +
		_from.identifier() +
		"_to_" +
		_to.identifier() +
		_options.toFunctionNameSuffix();
	solAssert(_from.isDynamicallySized() == _to.isDynamicallySized(), "");
	solAssert(_from.length() == _to.length(), "");
	solAssert(_from.dataStoredIn(DataLocation::Memory), "");
	solAssert(_from.isByteArray(), "");
	return createFunction(functionName, [&]() {
		solAssert(_to.isByteArray(), "");
		Whiskers templ(R"(
			function (value, pos) -> end {
				let length := (value)
				pos := (pos, length)
				(add(value, 0x20), pos, length)
				end := add(pos, )
			}
		)");
		templ("functionName", functionName);
		templ("lengthFun", arrayLengthFunction(_from));
		templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options));
		templ("copyFun", copyToMemoryFunction(false));
		templ("lengthPadded", _options.padded ? roundUpFunction() + "(length)" : "length");
		return templ.render();
	});
}
string ABIFunctions::abiEncodingFunctionCompactStorageArray(
	ArrayType const& _from,
	ArrayType const& _to,
	EncodingOptions const& _options
)
{
	string functionName =
		"abi_encode_" +
		_from.identifier() +
		"_to_" +
		_to.identifier() +
		_options.toFunctionNameSuffix();
	solAssert(_from.isDynamicallySized() == _to.isDynamicallySized(), "");
	solAssert(_from.length() == _to.length(), "");
	solAssert(_from.dataStoredIn(DataLocation::Storage), "");
	return createFunction(functionName, [&]() {
		if (_from.isByteArray())
		{
			solAssert(_to.isByteArray(), "");
			Whiskers templ(R"(
				//  -> 
				function (value, pos) -> ret {
					let slotValue := sload(value)
					switch and(slotValue, 1)
					case 0 {
						// short byte array
						let length := and(div(slotValue, 2), 0x7f)
						pos := (pos, length)
						mstore(pos, and(slotValue, not(0xff)))
						ret := add(pos, )
					}
					case 1 {
						// long byte array
						let length := div(slotValue, 2)
						pos := (pos, length)
						let dataPos := (value)
						let i := 0
						for { } lt(i, length) { i := add(i, 0x20) } {
							mstore(add(pos, i), sload(dataPos))
							dataPos := add(dataPos, 1)
						}
						ret := add(pos, )
					}
				}
			)");
			templ("functionName", functionName);
			templ("readableTypeNameFrom", _from.toString(true));
			templ("readableTypeNameTo", _to.toString(true));
			templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options));
			templ("lengthPaddedShort", _options.padded ? "0x20" : "length");
			templ("lengthPaddedLong", _options.padded ? "i" : "length");
			templ("arrayDataSlot", arrayDataAreaFunction(_from));
			return templ.render();
		}
		else
		{
			// Multiple items per slot
			solAssert(_from.baseType()->storageBytes() <= 16, "");
			solAssert(!_from.baseType()->isDynamicallyEncoded(), "");
			solAssert(_from.baseType()->isValueType(), "");
			bool dynamic = _to.isDynamicallyEncoded();
			size_t storageBytes = _from.baseType()->storageBytes();
			size_t itemsPerSlot = 32 / storageBytes;
			// This always writes full slot contents to memory, which might be
			// more than desired, i.e. it always writes beyond the end of memory.
			Whiskers templ(
				R"(
					//  -> 
					function (value, pos)  {
						let length := (value)
						pos := (pos, length)
						let originalPos := pos
						let srcPtr := (value)
						for { let i := 0 } lt(i, length) { i := add(i, ) }
						{
							let data := sload(srcPtr)
							<#items>
								((data), pos)
								pos := add(pos, )
							
							srcPtr := add(srcPtr, 1)
						}
						pos := add(originalPos, mul(length, ))
						
					}
				)"
			);
			templ("functionName", functionName);
			templ("readableTypeNameFrom", _from.toString(true));
			templ("readableTypeNameTo", _to.toString(true));
			templ("return", dynamic ? " -> end " : "");
			templ("assignEnd", dynamic ? "end := pos" : "");
			templ("lengthFun", arrayLengthFunction(_from));
			templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options));
			templ("dataArea", arrayDataAreaFunction(_from));
			templ("itemsPerSlot", to_string(itemsPerSlot));
			// We use padded size because array elements are always padded.
			string elementEncodedSize = toCompactHexWithPrefix(_to.baseType()->calldataEncodedSize());
			templ("elementEncodedSize", elementEncodedSize);
			EncodingOptions subOptions(_options);
			subOptions.encodeFunctionFromStack = false;
			subOptions.padded = true;
			string encodeToMemoryFun = abiEncodingFunction(
				*_from.baseType(),
				*_to.baseType(),
				subOptions
			);
			templ("encodeToMemoryFun", encodeToMemoryFun);
			std::vector> items(itemsPerSlot);
			for (size_t i = 0; i < itemsPerSlot; ++i)
				items[i]["shiftRightFun"] = shiftRightFunction(i * storageBytes * 8);
			templ("items", items);
			return templ.render();
		}
	});
}
string ABIFunctions::abiEncodingFunctionStruct(
	StructType const& _from,
	StructType const& _to,
	EncodingOptions const& _options
)
{
	string functionName =
		"abi_encode_" +
		_from.identifier() +
		"_to_" +
		_to.identifier() +
		_options.toFunctionNameSuffix();
	solUnimplementedAssert(!_from.dataStoredIn(DataLocation::CallData), "Encoding struct from calldata is not yet supported.");
	solAssert(&_from.structDefinition() == &_to.structDefinition(), "");
	return createFunction(functionName, [&]() {
		bool fromStorage = _from.location() == DataLocation::Storage;
		bool dynamic = _to.isDynamicallyEncoded();
		Whiskers templ(R"(
			//  -> 
			function (value, pos)  {
				let tail := add(pos, )
				
				<#members>
				{
					// 
					
					let memberValue := 
					
				}
				
				
			}
		)");
		templ("functionName", functionName);
		templ("readableTypeNameFrom", _from.toString(true));
		templ("readableTypeNameTo", _to.toString(true));
		templ("return", dynamic ? " -> end " : "");
		if (dynamic && _options.dynamicInplace)
			templ("assignEnd", "end := pos");
		else if (dynamic && !_options.dynamicInplace)
			templ("assignEnd", "end := tail");
		else
			templ("assignEnd", "");
		// to avoid multiple loads from the same slot for subsequent members
		templ("init", fromStorage ? "let slotValue := 0" : "");
		u256 previousSlotOffset(-1);
		u256 encodingOffset = 0;
		vector