/* 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::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(to == IntegerType::uint256(), ""); templ("cleanupConvert", "value"); } else { if (_from == to) templ("cleanupConvert", cleanupFunction(_from) + "(value)"); else templ("cleanupConvert", conversionFunction(_from, to) + "(value)"); } return templ.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, (length)) } )"); templ("storeLength", arrayStoreLengthForEncodingFunction(toArrayType)); templ("functionName", functionName); templ("readableTypeNameFrom", _from.toString(true)); templ("readableTypeNameTo", _to.toString(true)); templ("copyFun", copyToMemoryFunction(true)); templ("roundUpFun", roundUpFunction()); 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); Whiskers templ( dynamicBase ? 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) srcPtr := (srcPtr) pos := add(pos, ) } } )" ); 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)); templ("dataAreaFun", arrayDataAreaFunction(_from)); templ("elementEncodedSize", toCompactHexWithPrefix(_to.baseType()->calldataEncodedSize())); EncodingOptions subOptions(_options); subOptions.encodeFunctionFromStack = false; templ("encodeToMemoryFun", abiEncodingFunction( *_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) mstore(pos, length) (add(value, 0x20), add(pos, 0x20), length) end := add(add(pos, 0x20), (length)) } )"); templ("functionName", functionName); templ("lengthFun", arrayLengthFunction(_from)); templ("copyFun", copyToMemoryFunction(false)); templ("roundUpFun", roundUpFunction()); 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) mstore(pos, length) mstore(add(pos, 0x20), and(slotValue, not(0xff))) ret := add(pos, 0x40) } case 1 { // long byte array let length := div(slotValue, 2) mstore(pos, length) pos := add(pos, 0x20) 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, i) } } )"); templ("functionName", functionName); templ("readableTypeNameFrom", _from.toString(true)); templ("readableTypeNameTo", _to.toString(true)); 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 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)); templ("dataArea", arrayDataAreaFunction(_from)); templ("itemsPerSlot", to_string(itemsPerSlot)); string elementEncodedSize = toCompactHexWithPrefix(_to.baseType()->calldataEncodedSize()); templ("elementEncodedSize", elementEncodedSize); EncodingOptions subOptions(_options); subOptions.encodeFunctionFromStack = false; 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 " : ""); templ("assignEnd", dynamic ? "end := tail" : ""); // to avoid multiple loads from the same slot for subsequent members templ("init", fromStorage ? "let slotValue := 0" : ""); u256 previousSlotOffset(-1); u256 encodingOffset = 0; vector> members; for (auto const& member: _to.members(nullptr)) { solAssert(member.type, ""); if (!member.type->canLiveOutsideStorage()) continue; TypePointer memberTypeTo = member.type->fullEncodingType(_options.encodeAsLibraryTypes, true, false); solUnimplementedAssert(memberTypeTo, "Encoding type \"" + member.type->toString() + "\" not yet implemented."); auto memberTypeFrom = _from.memberType(member.name); solAssert(memberTypeFrom, ""); bool dynamicMember = memberTypeTo->isDynamicallyEncoded(); if (dynamicMember) solAssert(dynamic, ""); members.push_back({}); members.back()["preprocess"] = ""; if (fromStorage) { solAssert(memberTypeFrom->isValueType() == memberTypeTo->isValueType(), ""); u256 storageSlotOffset; size_t intraSlotOffset; tie(storageSlotOffset, intraSlotOffset) = _from.storageOffsetsOfMember(member.name); if (memberTypeFrom->isValueType()) { if (storageSlotOffset != previousSlotOffset) { members.back()["preprocess"] = "slotValue := sload(add(value, " + toCompactHexWithPrefix(storageSlotOffset) + "))"; previousSlotOffset = storageSlotOffset; } members.back()["retrieveValue"] = shiftRightFunction(intraSlotOffset * 8) + "(slotValue)"; } else { solAssert(memberTypeFrom->dataStoredIn(DataLocation::Storage), ""); solAssert(intraSlotOffset == 0, ""); members.back()["retrieveValue"] = "add(value, " + toCompactHexWithPrefix(storageSlotOffset) + ")"; } } else { string sourceOffset = toCompactHexWithPrefix(_from.memoryOffsetOfMember(member.name)); members.back()["retrieveValue"] = "mload(add(value, " + sourceOffset + "))"; } Whiskers encodeTempl( dynamicMember ? string(R"( mstore(add(pos, ), sub(tail, pos)) tail := (memberValue, tail) )") : string(R"( (memberValue, add(pos, )) )") ); encodeTempl("encodingOffset", toCompactHexWithPrefix(encodingOffset)); encodingOffset += dynamicMember ? 0x20 : memberTypeTo->calldataEncodedSize(); EncodingOptions subOptions(_options); subOptions.encodeFunctionFromStack = false; encodeTempl("abiEncode", abiEncodingFunction(*memberTypeFrom, *memberTypeTo, subOptions)); members.back()["encode"] = encodeTempl.render(); members.back()["memberName"] = member.name; } templ("members", members); templ("headSize", toCompactHexWithPrefix(encodingOffset)); return templ.render(); }); } string ABIFunctions::abiEncodingFunctionStringLiteral( Type const& _from, Type const& _to, EncodingOptions const& _options ) { solAssert(_from.category() == Type::Category::StringLiteral, ""); string functionName = "abi_encode_" + _from.identifier() + "_to_" + _to.identifier() + _options.toFunctionNameSuffix(); return createFunction(functionName, [&]() { auto const& strType = dynamic_cast(_from); string const& value = strType.value(); solAssert(_from.sizeOnStack() == 0, ""); if (_to.isDynamicallySized()) { Whiskers templ(R"( function (pos) -> end { mstore(pos, ) <#word> mstore(add(pos, ), ) end := add(pos, ) } )"); templ("functionName", functionName); // TODO this can make use of CODECOPY for large strings once we have that in Yul size_t words = (value.size() + 31) / 32; templ("overallSize", to_string(32 + words * 32)); templ("length", to_string(value.size())); vector> wordParams(words); for (size_t i = 0; i < words; ++i) { wordParams[i]["offset"] = to_string(32 + i * 32); wordParams[i]["wordValue"] = "0x" + h256(value.substr(32 * i, 32), h256::AlignLeft).hex(); } templ("word", wordParams); return templ.render(); } else { solAssert(_to.category() == Type::Category::FixedBytes, ""); solAssert(value.size() <= 32, ""); Whiskers templ(R"( function (pos) { mstore(pos, ) } )"); templ("functionName", functionName); templ("wordValue", "0x" + h256(value, h256::AlignLeft).hex()); return templ.render(); } }); } string ABIFunctions::abiEncodingFunctionFunctionType( FunctionType const& _from, Type const& _to, EncodingOptions const& _options ) { solAssert(_from.kind() == FunctionType::Kind::External, ""); solAssert(_from == _to, ""); string functionName = "abi_encode_" + _from.identifier() + "_to_" + _to.identifier() + _options.toFunctionNameSuffix(); if (_options.encodeFunctionFromStack) return createFunction(functionName, [&]() { return Whiskers(R"( function (addr, function_id, pos) { mstore(pos, (addr, function_id)) } )") ("functionName", functionName) ("combineExtFun", combineExternalFunctionIdFunction()) .render(); }); else return createFunction(functionName, [&]() { return Whiskers(R"( function (addr_and_function_id, pos) { mstore(pos, (addr_and_function_id)) } )") ("functionName", functionName) ("cleanExtFun", cleanupCombinedExternalFunctionIdFunction()) .render(); }); } string ABIFunctions::abiDecodingFunction(Type const& _type, bool _fromMemory, bool _forUseOnStack) { // The decoding function has to perform bounds checks unless it decodes a value type. // Conversely, bounds checks have to be performed before the decoding function // of a value type is called. TypePointer decodingType = _type.decodingType(); solAssert(decodingType, ""); if (auto arrayType = dynamic_cast(decodingType.get())) { if (arrayType->dataStoredIn(DataLocation::CallData)) { solAssert(!_fromMemory, ""); return abiDecodingFunctionCalldataArray(*arrayType); } else if (arrayType->isByteArray()) return abiDecodingFunctionByteArray(*arrayType, _fromMemory); else return abiDecodingFunctionArray(*arrayType, _fromMemory); } else if (auto const* structType = dynamic_cast(decodingType.get())) return abiDecodingFunctionStruct(*structType, _fromMemory); else if (auto const* functionType = dynamic_cast(decodingType.get())) return abiDecodingFunctionFunctionType(*functionType, _fromMemory, _forUseOnStack); else return abiDecodingFunctionValueType(_type, _fromMemory); } string ABIFunctions::abiDecodingFunctionValueType(const Type& _type, bool _fromMemory) { TypePointer decodingType = _type.decodingType(); solAssert(decodingType, ""); solAssert(decodingType->sizeOnStack() == 1, ""); solAssert(decodingType->isValueType(), ""); solAssert(decodingType->calldataEncodedSize() == 32, ""); solAssert(!decodingType->isDynamicallyEncoded(), ""); string functionName = "abi_decode_" + _type.identifier() + (_fromMemory ? "_fromMemory" : ""); return createFunction(functionName, [&]() { Whiskers templ(R"( function (offset, end) -> value { value := ((offset)) } )"); templ("functionName", functionName); templ("load", _fromMemory ? "mload" : "calldataload"); // Cleanup itself should use the type and not decodingType, because e.g. // the decoding type of an enum is a plain int. templ("cleanup", cleanupFunction(_type, true)); return templ.render(); }); } string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _fromMemory) { solAssert(_type.dataStoredIn(DataLocation::Memory), ""); solAssert(!_type.isByteArray(), ""); string functionName = "abi_decode_" + _type.identifier() + (_fromMemory ? "_fromMemory" : ""); solAssert(!_type.dataStoredIn(DataLocation::Storage), ""); return createFunction(functionName, [&]() { string load = _fromMemory ? "mload" : "calldataload"; bool dynamicBase = _type.baseType()->isDynamicallyEncoded(); Whiskers templ( R"( // function (offset, end) -> array { if iszero(slt(add(offset, 0x1f), end)) { revert(0, 0) } let length := array := ((length)) let dst := array // might update offset and dst let src := offset for { let i := 0 } lt(i, length) { i := add(i, 1) } { let elementPos := mstore(dst, (elementPos, end)) dst := add(dst, 0x20) src := add(src, ) } } )" ); templ("functionName", functionName); templ("readableTypeName", _type.toString(true)); templ("retrieveLength", !_type.isDynamicallySized() ? toCompactHexWithPrefix(_type.length()) : load + "(offset)"); templ("allocate", allocationFunction()); templ("allocationSize", arrayAllocationSizeFunction(_type)); if (_type.isDynamicallySized()) templ("storeLength", "mstore(array, length) offset := add(offset, 0x20) dst := add(dst, 0x20)"); else templ("storeLength", ""); if (dynamicBase) { templ("staticBoundsCheck", ""); templ("retrieveElementPos", "add(offset, " + load + "(src))"); templ("baseEncodedSize", "0x20"); } else { string baseEncodedSize = toCompactHexWithPrefix(_type.baseType()->calldataEncodedSize()); templ("staticBoundsCheck", "if gt(add(src, mul(length, " + baseEncodedSize + ")), end) { revert(0, 0) }"); templ("retrieveElementPos", "src"); templ("baseEncodedSize", baseEncodedSize); } templ("decodingFun", abiDecodingFunction(*_type.baseType(), _fromMemory, false)); return templ.render(); }); } string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type) { // This does not work with arrays of complex types - the array access // is not yet implemented in Solidity. solAssert(_type.dataStoredIn(DataLocation::CallData), ""); if (!_type.isDynamicallySized()) solAssert(_type.length() < u256("0xffffffffffffffff"), ""); if (_type.baseType()->isDynamicallyEncoded()) solUnimplemented("Calldata arrays with non-value base types are not yet supported by Solidity."); solAssert(_type.baseType()->calldataEncodedSize() < u256("0xffffffffffffffff"), ""); string functionName = "abi_decode_" + _type.identifier(); return createFunction(functionName, [&]() { string templ; if (_type.isDynamicallySized()) templ = R"( // function (offset, end) -> arrayPos, length { if iszero(slt(add(offset, 0x1f), end)) { revert(0, 0) } length := calldataload(offset) if gt(length, 0xffffffffffffffff) { revert(0, 0) } arrayPos := add(offset, 0x20) if gt(add(arrayPos, mul(, )), end) { revert(0, 0) } } )"; else templ = R"( // function (offset, end) -> arrayPos { arrayPos := offset if gt(add(arrayPos, mul(, )), end) { revert(0, 0) } } )"; Whiskers w{templ}; w("functionName", functionName); w("readableTypeName", _type.toString(true)); w("baseEncodedSize", toCompactHexWithPrefix(_type.isByteArray() ? 1 : _type.baseType()->calldataEncodedSize())); w("length", _type.isDynamicallyEncoded() ? "length" : toCompactHexWithPrefix(_type.length())); return w.render(); }); } string ABIFunctions::abiDecodingFunctionByteArray(ArrayType const& _type, bool _fromMemory) { solAssert(_type.dataStoredIn(DataLocation::Memory), ""); solAssert(_type.isByteArray(), ""); string functionName = "abi_decode_" + _type.identifier() + (_fromMemory ? "_fromMemory" : ""); return createFunction(functionName, [&]() { Whiskers templ( R"( function (offset, end) -> array { if iszero(slt(add(offset, 0x1f), end)) { revert(0, 0) } let length := (offset) array := ((length)) mstore(array, length) let src := add(offset, 0x20) let dst := add(array, 0x20) if gt(add(src, length), end) { revert(0, 0) } (src, dst, length) } )" ); templ("functionName", functionName); templ("load", _fromMemory ? "mload" : "calldataload"); templ("allocate", allocationFunction()); templ("allocationSize", arrayAllocationSizeFunction(_type)); templ("copyToMemFun", copyToMemoryFunction(!_fromMemory)); return templ.render(); }); } string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fromMemory) { string functionName = "abi_decode_" + _type.identifier() + (_fromMemory ? "_fromMemory" : ""); solUnimplementedAssert(!_type.dataStoredIn(DataLocation::CallData), ""); return createFunction(functionName, [&]() { Whiskers templ(R"( // function (headStart, end) -> value { if slt(sub(end, headStart), ) { revert(0, 0) } value := () <#members> { // } } )"); templ("functionName", functionName); templ("readableTypeName", _type.toString(true)); templ("allocate", allocationFunction()); solAssert(_type.memorySize() < u256("0xffffffffffffffff"), ""); templ("memorySize", toCompactHexWithPrefix(_type.memorySize())); size_t headPos = 0; vector> members; for (auto const& member: _type.members(nullptr)) { solAssert(member.type, ""); solAssert(member.type->canLiveOutsideStorage(), ""); auto decodingType = member.type->decodingType(); solAssert(decodingType, ""); bool dynamic = decodingType->isDynamicallyEncoded(); Whiskers memberTempl( dynamic ? R"( let offset := (add(headStart, )) if gt(offset, 0xffffffffffffffff) { revert(0, 0) } mstore(add(value, ), (add(headStart, offset), end)) )" : R"( let offset := mstore(add(value, ), (add(headStart, offset), end)) )" ); memberTempl("load", _fromMemory ? "mload" : "calldataload"); memberTempl("pos", to_string(headPos)); memberTempl("memoryOffset", toCompactHexWithPrefix(_type.memoryOffsetOfMember(member.name))); memberTempl("abiDecode", abiDecodingFunction(*member.type, _fromMemory, false)); members.push_back({}); members.back()["decode"] = memberTempl.render(); members.back()["memberName"] = member.name; headPos += dynamic ? 0x20 : decodingType->calldataEncodedSize(); } templ("members", members); templ("minimumSize", toCompactHexWithPrefix(headPos)); return templ.render(); }); } string ABIFunctions::abiDecodingFunctionFunctionType(FunctionType const& _type, bool _fromMemory, bool _forUseOnStack) { solAssert(_type.kind() == FunctionType::Kind::External, ""); string functionName = "abi_decode_" + _type.identifier() + (_fromMemory ? "_fromMemory" : "") + (_forUseOnStack ? "_onStack" : ""); return createFunction(functionName, [&]() { if (_forUseOnStack) { return Whiskers(R"( function (offset, end) -> addr, function_selector { addr, function_selector := ((offset)) } )") ("functionName", functionName) ("load", _fromMemory ? "mload" : "calldataload") ("splitExtFun", splitExternalFunctionIdFunction()) .render(); } else { return Whiskers(R"( function (offset, end) -> fun { fun := ((offset)) } )") ("functionName", functionName) ("load", _fromMemory ? "mload" : "calldataload") ("cleanExtFun", cleanupCombinedExternalFunctionIdFunction()) .render(); } }); } string ABIFunctions::copyToMemoryFunction(bool _fromCalldata) { string functionName = "copy_" + string(_fromCalldata ? "calldata" : "memory") + "_to_memory"; return createFunction(functionName, [&]() { if (_fromCalldata) { return Whiskers(R"( function (src, dst, length) { calldatacopy(dst, src, length) // clear end mstore(add(dst, length), 0) } )") ("functionName", functionName) .render(); } else { return Whiskers(R"( function (src, dst, length) { let i := 0 for { } lt(i, length) { i := add(i, 32) } { mstore(add(dst, i), mload(add(src, i))) } if gt(i, length) { // clear end mstore(add(dst, length), 0) } } )") ("functionName", functionName) .render(); } }); } string ABIFunctions::shiftLeftFunction(size_t _numBits) { solAssert(_numBits < 256, ""); string functionName = "shift_left_" + to_string(_numBits); if (m_evmVersion.hasBitwiseShifting()) { return createFunction(functionName, [&]() { return Whiskers(R"( function (value) -> newValue { newValue := shl(, value) } )") ("functionName", functionName) ("numBits", to_string(_numBits)) .render(); }); } else { return createFunction(functionName, [&]() { return Whiskers(R"( function (value) -> newValue { newValue := mul(value, ) } )") ("functionName", functionName) ("multiplier", toCompactHexWithPrefix(u256(1) << _numBits)) .render(); }); } } string ABIFunctions::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 createFunction(functionName, [&]() { return Whiskers(R"( function (value) -> newValue { newValue := shr(, value) } )") ("functionName", functionName) ("numBits", to_string(_numBits)) .render(); }); } else { return createFunction(functionName, [&]() { return Whiskers(R"( function (value) -> newValue { newValue := div(value, ) } )") ("functionName", functionName) ("multiplier", toCompactHexWithPrefix(u256(1) << _numBits)) .render(); }); } } string ABIFunctions::roundUpFunction() { string functionName = "round_up_to_mul_of_32"; return createFunction(functionName, [&]() { return Whiskers(R"( function (value) -> result { result := and(add(value, 31), not(31)) } )") ("functionName", functionName) .render(); }); } string ABIFunctions::arrayLengthFunction(ArrayType const& _type) { string functionName = "array_length_" + _type.identifier(); return createFunction(functionName, [&]() { Whiskers w(R"( function (value) -> length { } )"); 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 ABIFunctions::arrayAllocationSizeFunction(ArrayType const& _type) { solAssert(_type.dataStoredIn(DataLocation::Memory), ""); string functionName = "array_allocation_size_" + _type.identifier(); return createFunction(functionName, [&]() { Whiskers w(R"( function (length) -> size { // Make sure we can allocate memory without overflow if gt(length, 0xffffffffffffffff) { revert(0, 0) } size := } )"); 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 ABIFunctions::arrayDataAreaFunction(ArrayType const& _type) { string functionName = "array_dataslot_" + _type.identifier(); return createFunction(functionName, [&]() { if (_type.dataStoredIn(DataLocation::Memory)) { if (_type.isDynamicallySized()) return Whiskers(R"( function (memPtr) -> dataPtr { dataPtr := add(memPtr, 0x20) } )") ("functionName", functionName) .render(); else return Whiskers(R"( function (memPtr) -> dataPtr { dataPtr := memPtr } )") ("functionName", functionName) .render(); } else if (_type.dataStoredIn(DataLocation::Storage)) { if (_type.isDynamicallySized()) { Whiskers w(R"( function (slot) -> dataSlot { mstore(0, slot) dataSlot := keccak256(0, 0x20) } )"); w("functionName", functionName); return w.render(); } else { Whiskers w(R"( function (slot) -> dataSlot { dataSlot := slot } )"); w("functionName", functionName); return w.render(); } } else { // Not used for calldata solAssert(false, ""); } }); } string ABIFunctions::nextArrayElementFunction(ArrayType const& _type) { solAssert(!_type.isByteArray(), ""); solAssert( _type.location() == DataLocation::Memory || _type.location() == DataLocation::Storage, "" ); solAssert( _type.location() == DataLocation::Memory || _type.baseType()->storageBytes() > 16, "" ); string functionName = "array_nextElement_" + _type.identifier(); return createFunction(functionName, [&]() { if (_type.location() == DataLocation::Memory) return Whiskers(R"( function (memPtr) -> nextPtr { nextPtr := add(memPtr, 0x20) } )") ("functionName", functionName) .render(); else if (_type.location() == DataLocation::Storage) return Whiskers(R"( function (slot) -> nextSlot { nextSlot := add(slot, 1) } )") ("functionName", functionName) .render(); else solAssert(false, ""); }); } string ABIFunctions::arrayStoreLengthForEncodingFunction(ArrayType const& _type) { string functionName = "array_storeLengthForEncoding_" + _type.identifier(); return createFunction(functionName, [&]() { if (_type.isDynamicallySized()) return Whiskers(R"( function (pos, length) -> updated_pos { mstore(pos, length) updated_pos := add(pos, 0x20) } )") ("functionName", functionName) .render(); else return Whiskers(R"( function (pos, length) -> updated_pos { updated_pos := pos } )") ("functionName", functionName) .render(); }); } string ABIFunctions::allocationFunction() { string functionName = "allocateMemory"; return createFunction(functionName, [&]() { return Whiskers(R"( function (size) -> memPtr { memPtr := mload() let newFreePtr := add(memPtr, size) // protect against overflow if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { revert(0, 0) } mstore(, newFreePtr) } )") ("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer)) ("functionName", functionName) .render(); }); } string ABIFunctions::createFunction(string const& _name, function const& _creator) { if (!m_requestedFunctions.count(_name)) { auto fun = _creator(); solAssert(!fun.empty(), ""); m_requestedFunctions[_name] = fun; } return _name; } string ABIFunctions::createExternallyUsedFunction(string const& _name, function const& _creator) { string name = createFunction(_name, _creator); m_externallyUsedFunctions.insert(name); return name; } size_t ABIFunctions::headSize(TypePointers const& _targetTypes) { size_t headSize = 0; for (auto const& t: _targetTypes) { if (t->isDynamicallyEncoded()) headSize += 0x20; else headSize += t->calldataEncodedSize(); } return headSize; }