/*
	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 
#include 
using namespace std;
using namespace solidity;
using namespace solidity::util;
using namespace solidity::frontend;
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, [&]() {
		// 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 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();
			bool dynamic = _targetTypes[i]->isDynamicallyEncoded();
			Whiskers elementTempl(
				dynamic ?
				string(R"(
					mstore(add(headStart, ), sub(tail, headStart))
					tail := ( tail)
				)") :
				string(R"(
					( add(headStart, ))
				)")
			);
			string values = suffixedVariableNameList("value", stackPos, stackPos + sizeOnStack);
			elementTempl("values", values.empty() ? "" : values + ", ");
			elementTempl("pos", to_string(headPos));
			elementTempl("abiEncode", abiEncodingFunction(*_givenTypes[i], *_targetTypes[i], options));
			encodeElements += elementTempl.render();
			headPos += _targetTypes[i]->calldataHeadSize();
			stackPos += sizeOnStack;
		}
		solAssert(headPos == headSize_, "");
		string valueParams = suffixedVariableNameList("value", stackPos, 0);
		templ("valueParams", valueParams.empty() ? "" : ", " + 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 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();
			bool dynamic = _targetTypes[i]->isDynamicallyEncoded();
			Whiskers elementTempl(
				dynamic ?
				string(R"(
					pos := ( pos)
				)") :
				string(R"(
					( pos)
					pos := add(pos, )
				)")
			);
			string values = suffixedVariableNameList("value", stackPos, stackPos + sizeOnStack);
			elementTempl("values", values.empty() ? "" : values + ", ");
			if (!dynamic)
				elementTempl("calldataEncodedSize", to_string(_targetTypes[i]->calldataEncodedSize(false)));
			elementTempl("abiEncode", abiEncodingFunction(*_givenTypes[i], *_targetTypes[i], options));
			encodeElements += elementTempl.render();
			stackPos += sizeOnStack;
		}
		string valueParams = suffixedVariableNameList("value", stackPos, 0);
		templ("valueParams", valueParams.empty() ? "" : ", " + 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";
	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 += decodingTypes[i]->calldataHeadSize();
		}
		templ("valueReturnParams", boost::algorithm::join(valueReturnParams, ", "));
		templ("arrow", valueReturnParams.empty() ? "" : "->");
		templ("decodeElements", decodeElements);
		return templ.render();
	});
}
pair> ABIFunctions::requestedFunctions()
{
	std::set empty;
	swap(empty, m_externallyUsedFunctions);
	return make_pair(m_functionCollector->requestedFunctions(), std::move(empty));
}
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::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);
		switch (fromArray.location())
		{
			case DataLocation::CallData:
				if (
					fromArray.isByteArray() ||
					*fromArray.baseType() == *TypeProvider::uint256() ||
					*fromArray.baseType() == FixedBytesType(32)
				)
					return abiEncodingFunctionCalldataArrayWithoutCleanup(fromArray, *toArray, _options);
				else
					return abiEncodingFunctionSimpleArray(fromArray, *toArray, _options);
			case DataLocation::Memory:
				if (fromArray.isByteArray())
					return abiEncodingFunctionMemoryByteArray(fromArray, *toArray, _options);
				else
					return abiEncodingFunctionSimpleArray(fromArray, *toArray, _options);
			case DataLocation::Storage:
				if (fromArray.baseType()->storageBytes() <= 16)
					return abiEncodingFunctionCompactStorageArray(fromArray, *toArray, _options);
				else
					return abiEncodingFunctionSimpleArray(fromArray, *toArray, _options);
			default:
				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 == *TypeProvider::uint256(), "");
			templ("cleanupConvert", "value");
		}
		else
		{
			string cleanupConvert;
			if (_from == to)
				cleanupConvert = m_utils.cleanupFunction(_from) + "(value)";
			else
				cleanupConvert = m_utils.conversionFunction(_from, to) + "(value)";
			if (!_options.padded)
				cleanupConvert = m_utils.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 values = suffixedVariableNameList("value", 0, numVariablesForType(_givenType, _options));
		string encoder = abiEncodingFunction(_givenType, _targetType, _options);
		if (_targetType.isDynamicallyEncoded())
			return Whiskers(R"(
				function (, pos) -> updatedPos {
					updatedPos := (, pos)
				}
			)")
			("functionName", functionName)
			("encode", encoder)
			("values", values)
			.render();
		else
		{
			unsigned encodedSize = _targetType.calldataEncodedSize(_options.padded);
			solAssert(encodedSize != 0, "Invalid encoded size.");
			return Whiskers(R"(
				function (, pos) -> updatedPos {
					(, pos)
					updatedPos := add(pos, )
				}
			)")
			("functionName", functionName)
			("encode", encoder)
			("encodedSize", toCompactHexWithPrefix(encodedSize))
			("values", values)
			.render();
		}
	});
}
string ABIFunctions::abiEncodingFunctionCalldataArrayWithoutCleanup(
	Type const& _from,
	Type const& _to,
	EncodingOptions const& _options
)
{
	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.isByteArray() ||
		*fromArrayType.baseType() == *TypeProvider::uint256() ||
		*fromArrayType.baseType() == FixedBytesType(32),
		""
	);
	solAssert(fromArrayType.calldataStride() == toArrayType.memoryStride(), "");
	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, [&]() {
		bool needsPadding = _options.padded && fromArrayType.isByteArray();
		if (fromArrayType.isDynamicallySized())
		{
			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);
			if (fromArrayType.isByteArray() || fromArrayType.calldataStride() == 1)
				templ("scaleLengthByStride", "");
			else
				templ("scaleLengthByStride",
					Whiskers(R"(
						if gt(length, ) { revert(0, 0) }
						length := mul(length, )
					)")
					("stride", toCompactHexWithPrefix(fromArrayType.calldataStride()))
					("maxLength", toCompactHexWithPrefix(u256(-1) / fromArrayType.calldataStride()))
					.render()
				);
			templ("readableTypeNameFrom", _from.toString(true));
			templ("readableTypeNameTo", _to.toString(true));
			templ("copyFun", m_utils.copyToMemoryFunction(true));
			templ("lengthPadded", needsPadding ? m_utils.roundUpFunction() + "(length)" : "length");
			return templ.render();
		}
		else
		{
			solAssert(fromArrayType.calldataStride() == 32, "");
			Whiskers templ(R"(
				//  -> 
				function (start, pos) {
					(start, pos, )
				}
			)");
			templ("functionName", functionName);
			templ("readableTypeNameFrom", _from.toString(true));
			templ("readableTypeNameTo", _to.toString(true));
			templ("copyFun", m_utils.copyToMemoryFunction(true));
			templ("byteLength", toCompactHexWithPrefix(fromArrayType.length() * fromArrayType.calldataStride()));
			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.isByteArray(), "");
	if (_from.dataStoredIn(DataLocation::Storage))
		solAssert(_from.baseType()->storageBytes() > 16, "");
	return createFunction(functionName, [&]() {
		bool dynamic = _to.isDynamicallyEncoded();
		bool dynamicBase = _to.baseType()->isDynamicallyEncoded();
		bool const usesTail = dynamicBase && !_options.dynamicInplace;
		EncodingOptions subOptions(_options);
		subOptions.encodeFunctionFromStack = false;
		subOptions.padded = true;
		string elementValues = suffixedVariableNameList("elementValue", 0, numVariablesForType(*_from.baseType(), subOptions));
		Whiskers templ(
			usesTail ?
			R"(
				//  -> 
				function (value, pos)  {
					
					pos := (pos, length)
					let headStart := pos
					let tail := add(pos, mul(length, 0x20))
					let baseRef := (value)
					let srcPtr := baseRef
					for { let i := 0 } lt(i, length) { i := add(i, 1) }
					{
						mstore(pos, sub(tail, headStart))
						let  := 
						tail := (, tail)
						srcPtr := (srcPtr)
						pos := add(pos, 0x20)
					}
					pos := tail
					
				}
			)" :
			R"(
				//  -> 
				function (value, pos)  {
					
					pos := (pos, length)
					let baseRef := (value)
					let srcPtr := baseRef
					for { let i := 0 } lt(i, length) { i := add(i, 1) }
					{
						let  := 
						pos := (, pos)
						srcPtr := (srcPtr)
					}
					
				}
			)"
		);
		templ("functionName", functionName);
		templ("elementValues", elementValues);
		bool lengthAsArgument = _from.dataStoredIn(DataLocation::CallData) && _from.isDynamicallySized();
		if (lengthAsArgument)
		{
			templ("maybeLength", " length,");
			templ("declareLength", "");
		}
		else
		{
			templ("maybeLength", "");
			templ("declareLength", "let length := " + m_utils.arrayLengthFunction(_from) + "(value)");
		}
		templ("readableTypeNameFrom", _from.toString(true));
		templ("readableTypeNameTo", _to.toString(true));
		templ("return", dynamic ? " -> end " : "");
		templ("assignEnd", dynamic ? "end := pos" : "");
		templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options));
		templ("dataAreaFun", m_utils.arrayDataAreaFunction(_from));
		templ("encodeToMemoryFun", abiEncodeAndReturnUpdatedPosFunction(*_from.baseType(), *_to.baseType(), subOptions));
		switch (_from.location())
		{
			case DataLocation::Memory:
				templ("arrayElementAccess", "mload(srcPtr)");
				break;
			case DataLocation::Storage:
				if (_from.baseType()->isValueType())
					templ("arrayElementAccess", m_utils.readFromStorage(*_from.baseType(), 0, false) + "(srcPtr)");
				else
					templ("arrayElementAccess", "srcPtr");
				break;
			case DataLocation::CallData:
				templ("arrayElementAccess", calldataAccessFunction(*_from.baseType()) + "(baseRef, srcPtr)");
				break;
			default:
				solAssert(false, "");
		}
		templ("nextArrayElement", m_utils.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", m_utils.arrayLengthFunction(_from));
		templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options));
		templ("copyFun", m_utils.copyToMemoryFunction(false));
		templ("lengthPadded", _options.padded ? m_utils.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", m_utils.arrayDataAreaFunction(_from));
			return templ.render();
		}
		else
		{
			// Multiple items per slot
			solAssert(_from.baseType()->storageBytes() <= 16, "");
			solAssert(!_from.baseType()->isDynamicallyEncoded(), "");
			solAssert(!_to.baseType()->isDynamicallyEncoded(), "");
			solAssert(_from.baseType()->isValueType(), "");
			bool dynamic = _to.isDynamicallyEncoded();
			size_t storageBytes = _from.baseType()->storageBytes();
			size_t itemsPerSlot = 32 / storageBytes;
			solAssert(itemsPerSlot > 0, "");
			// The number of elements we need to handle manually after the loop.
			size_t spill = size_t(_from.length() % itemsPerSlot);
			Whiskers templ(
				R"(
					//  -> 
					function (value, pos)  {
						let length := (value)
						pos := (pos, length)
						let originalPos := pos
						let srcPtr := (value)
						let itemCounter := 0
						if  {
							// Run the loop over all full slots
							for { } lt(add(itemCounter, sub(, 1)), length)
										{ itemCounter := add(itemCounter, ) }
							{
								let data := sload(srcPtr)
								<#items>
									((data), pos)
									pos := add(pos, )
								
								srcPtr := add(srcPtr, 1)
							}
						}
						// Handle the last (not necessarily full) slot specially
						if  {
							let data := sload(srcPtr)
							<#items>
								if  {
									((data), pos)
									pos := add(pos, )
									itemCounter := add(itemCounter, 1)
								}
							
						}
						
					}
				)"
			);
			templ("functionName", functionName);
			templ("readableTypeNameFrom", _from.toString(true));
			templ("readableTypeNameTo", _to.toString(true));
			templ("return", dynamic ? " -> end " : "");
			templ("assignEnd", dynamic ? "end := pos" : "");
			templ("lengthFun", m_utils.arrayLengthFunction(_from));
			templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options));
			templ("dataArea", m_utils.arrayDataAreaFunction(_from));
			// We skip the loop for arrays that fit a single slot.
			if (_from.isDynamicallySized() || _from.length() >= itemsPerSlot)
				templ("useLoop", "1");
			else
				templ("useLoop", "0");
			if (_from.isDynamicallySized() || spill != 0)
				templ("useSpill", "1");
			else
				templ("useSpill", "0");
			templ("itemsPerSlot", to_string(itemsPerSlot));
			templ("stride", toCompactHexWithPrefix(_to.calldataStride()));
			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)
			{
				if (_from.isDynamicallySized())
					items[i]["inRange"] = "lt(itemCounter, length)";
				else if (i < spill)
					items[i]["inRange"] = "1";
				else
					items[i]["inRange"] = "0";
				items[i]["extractFromSlot"] = m_utils.extractFromStorageValue(*_from.baseType(), i * storageBytes, false);
			}
			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();
	solAssert(&_from.structDefinition() == &_to.structDefinition(), "");
	return createFunction(functionName, [&]() {
		bool dynamic = _to.isDynamicallyEncoded();
		Whiskers templ(R"(
			//  -> 
			function (value, pos)  {
				let tail := add(pos, )
				
				<#members>
				{
					// 
					
					let  := 
					
				}
				
				
			}
		)");
		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", _from.dataStoredIn(DataLocation::Storage) ? "let slotValue := 0" : "");
		u256 previousSlotOffset(-1);
		u256 encodingOffset = 0;
		vector