diff --git a/libevmasm/Instruction.cpp b/libevmasm/Instruction.cpp index f68aa0a..6b664f5 100644 --- a/libevmasm/Instruction.cpp +++ b/libevmasm/Instruction.cpp @@ -318,6 +318,11 @@ static std::map const c_instructionInfo = { Instruction::RETURN, { "RETURN", 0, 2, 0, true, Tier::Zero } }, { Instruction::DELEGATECALL, { "DELEGATECALL", 0, 6, 1, true, Tier::Special } }, + + // Solidity++: + { Instruction::SYNCCALL, { "SYNCCALL", 0, 6, 0, true, Tier::Special } }, + { Instruction::CALLBACKDEST, { "CALLBACKDEST", 0, 0, 0, true, Tier::Special } }, + { Instruction::STATICCALL, { "STATICCALL", 0, 6, 1, true, Tier::Special } }, { Instruction::CREATE2, { "CREATE2", 0, 4, 1, true, Tier::Special } }, { Instruction::REVERT, { "REVERT", 0, 2, 0, true, Tier::Zero } }, diff --git a/libevmasm/Instruction.h b/libevmasm/Instruction.h index c405384..2927dcf 100644 --- a/libevmasm/Instruction.h +++ b/libevmasm/Instruction.h @@ -184,11 +184,15 @@ enum class Instruction: uint8_t SELFBALANCE, ///< get balance of the current account CREATE = 0xf0, ///< create a new account with associated code - CALL, ///< message-call into an account + CALL, ///< Async-call into a contract. Will not return data to the caller. CALLCODE, ///< message-call with another account's code only - RETURN, ///< halt execution returning output data + RETURN, ///< Halt execution returning output data. Will send a callback transaction to the caller of the sync-call. DELEGATECALL, ///< like CALLCODE but keeps caller's value and sender CREATE2 = 0xf5, ///< create new account with associated code at address `sha3(0xff + sender + salt + init code) % 2**160` + + SYNCCALL = 0xf7, ///< Sync-call into a contract. The callee will send a callback transaction to the caller while executing RETURN. + CALLBACKDEST = 0xf8,///< restore execution context for a sync-call + STATICCALL = 0xfa, ///< like CALL but disallow state modifications REVERT = 0xfd, ///< halt execution, revert state and return output data diff --git a/libsolidity/CMakeLists.txt b/libsolidity/CMakeLists.txt index 6eb80dc..6a4d7dd 100644 --- a/libsolidity/CMakeLists.txt +++ b/libsolidity/CMakeLists.txt @@ -59,8 +59,8 @@ set(sources ast/Types.h ast/TypeProvider.cpp ast/TypeProvider.h - ${ORIGINAL_SOURCE_DIR}/codegen/ABIFunctions.cpp - ${ORIGINAL_SOURCE_DIR}/codegen/ABIFunctions.h + codegen/ABIFunctions.cpp + codegen/ABIFunctions.h ${ORIGINAL_SOURCE_DIR}/codegen/ArrayUtils.cpp ${ORIGINAL_SOURCE_DIR}/codegen/ArrayUtils.h codegen/Compiler.cpp diff --git a/libsolidity/codegen/ABIFunctions.cpp b/libsolidity/codegen/ABIFunctions.cpp new file mode 100644 index 0000000..0355413 --- /dev/null +++ b/libsolidity/codegen/ABIFunctions.cpp @@ -0,0 +1,1541 @@ +// SPDX-License-Identifier: GPL-3.0 +/** + * @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 solidity; +using namespace solidity::util; +using namespace solidity::frontend; + +string ABIFunctions::tupleEncoder( + TypePointers const& _givenTypes, + TypePointers _targetTypes, + bool _encodeAsLibraryTypes, + bool _reversed +) +{ + solAssert(_givenTypes.size() == _targetTypes.size(), ""); + EncodingOptions options; + options.encodeAsLibraryTypes = _encodeAsLibraryTypes; + options.encodeFunctionFromStack = true; + options.padded = true; + options.dynamicInplace = false; + + for (Type const*& t: _targetTypes) + { + solAssert(t, ""); + t = t->fullEncodingType(options.encodeAsLibraryTypes, true, !options.padded); + solAssert(t, ""); + } + + 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(); + if (_reversed) + functionName += "_reversed"; + + return createFunction(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 = + _reversed ? + suffixedVariableNameList("value", stackPos, 0) : + suffixedVariableNameList("value", 0, stackPos); + templ("valueParams", valueParams.empty() ? "" : ", " + valueParams); + templ("encodeElements", encodeElements); + + return templ.render(); + }); +} + +string ABIFunctions::tupleEncoderPacked( + TypePointers const& _givenTypes, + TypePointers _targetTypes, + bool _reversed +) +{ + EncodingOptions options; + options.encodeAsLibraryTypes = false; + options.encodeFunctionFromStack = true; + options.padded = false; + options.dynamicInplace = true; + + for (Type const*& t: _targetTypes) + { + solAssert(t, ""); + t = t->fullEncodingType(options.encodeAsLibraryTypes, true, !options.padded); + solAssert(t, ""); + } + + 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(); + if (_reversed) + functionName += "_reversed"; + + return createFunction(functionName, [&]() { + // 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 = + _reversed ? + suffixedVariableNameList("value", stackPos, 0) : + suffixedVariableNameList("value", 0, stackPos); + 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 createFunction(functionName, [&]() { + TypePointers decodingTypes; + for (auto const& t: _types) + decodingTypes.emplace_back(t->decodingType()); + + Whiskers templ(R"( + function (headStart, dataEnd) { + if slt(sub(dataEnd, headStart), ) { } + + } + )"); + templ("functionName", functionName); + templ("revertString", revertReasonIfDebug("ABI decoding: tuple data too short")); + 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++; + } + Whiskers elementTempl(R"( + { + + let offset := (add(headStart, )) + if gt(offset, 0xffffffffffffffff) { } + + let offset := + + := (add(headStart, offset), dataEnd) + } + )"); + elementTempl("dynamic", decodingTypes[i]->isDynamicallyEncoded()); + // TODO add test + elementTempl("revertString", revertReasonIfDebug("ABI decoding: invalid tuple offset")); + 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(); + }); +} + +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)) + { + ArrayType const* fromArray = nullptr; + switch (_from.category()) + { + case Type::Category::Array: + fromArray = dynamic_cast(&_from); + break; + case Type::Category::ArraySlice: + fromArray = &dynamic_cast(&_from)->arrayType(); + solAssert( + fromArray->dataStoredIn(DataLocation::CallData) && + fromArray->isDynamicallySized() && + !fromArray->baseType()->isDynamicallyEncoded(), + "" + ); + break; + default: + solAssert(false, ""); + break; + } + + 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); + Type const* targetEncoding = _targetType.fullEncodingType(_options.encodeAsLibraryTypes, true, false); + solAssert(targetEncoding, ""); + if (targetEncoding->isDynamicallyEncoded()) + return Whiskers(R"( + function (, pos) -> updatedPos { + updatedPos := (, pos) + } + )") + ("functionName", functionName) + ("encode", encoder) + ("values", values) + .render(); + else + { + unsigned encodedSize = targetEncoding->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, ) { } + length := mul(length, ) + )") + ("stride", toCompactHexWithPrefix(fromArrayType.calldataStride())) + ("maxLength", toCompactHexWithPrefix(u256(-1) / fromArrayType.calldataStride())) + ("revertString", revertReasonIfDebug("ABI encoding: array data too long")) + .render() + // TODO add revert test + ); + 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) + let length := (slotValue) + pos := (pos, length) + switch and(slotValue, 1) + case 0 { + // short byte array + mstore(pos, and(slotValue, not(0xff))) + ret := add(pos, ) + } + case 1 { + // long byte array + 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("byteArrayLengthFunction", m_utils.extractByteArrayLengthFunction()); + 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 = static_cast(_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); + } + 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> members; + for (auto const& member: _to.members(nullptr)) + { + solAssert(member.type, ""); + solAssert(!member.type->containsNestedMapping(), ""); + 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.emplace_back(); + members.back()["preprocess"] = ""; + + switch (_from.location()) + { + case DataLocation::Storage: + { + 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"] = m_utils.extractFromStorageValue(*memberTypeFrom, intraSlotOffset) + "(slotValue)"; + } + else + { + solAssert(memberTypeFrom->dataStoredIn(DataLocation::Storage), ""); + solAssert(intraSlotOffset == 0, ""); + members.back()["retrieveValue"] = "add(value, " + toCompactHexWithPrefix(storageSlotOffset) + ")"; + } + break; + } + case DataLocation::Memory: + { + string sourceOffset = toCompactHexWithPrefix(_from.memoryOffsetOfMember(member.name)); + members.back()["retrieveValue"] = "mload(add(value, " + sourceOffset + "))"; + break; + } + case DataLocation::CallData: + { + string sourceOffset = toCompactHexWithPrefix(_from.calldataOffsetOfMember(member.name)); + members.back()["retrieveValue"] = calldataAccessFunction(*memberTypeFrom) + "(value, add(value, " + sourceOffset + "))"; + break; + } + default: + solAssert(false, ""); + } + + EncodingOptions subOptions(_options); + subOptions.encodeFunctionFromStack = false; + // Like with arrays, struct members are always padded. + subOptions.padded = true; + + string memberValues = suffixedVariableNameList("memberValue", 0, numVariablesForType(*memberTypeFrom, subOptions)); + members.back()["memberValues"] = memberValues; + + string encode; + if (_options.dynamicInplace) + encode = Whiskers{"pos := (, pos)"} + ("encode", abiEncodeAndReturnUpdatedPosFunction(*memberTypeFrom, *memberTypeTo, subOptions)) + ("memberValues", memberValues) + .render(); + else + { + Whiskers encodeTempl( + dynamicMember ? + string(R"( + mstore(add(pos, ), sub(tail, pos)) + tail := (, tail) + )") : + "(, add(pos, ))" + ); + encodeTempl("memberValues", memberValues); + encodeTempl("encodingOffset", toCompactHexWithPrefix(encodingOffset)); + encodingOffset += memberTypeTo->calldataHeadSize(); + encodeTempl("abiEncode", abiEncodingFunction(*memberTypeFrom, *memberTypeTo, subOptions)); + encode = encodeTempl.render(); + } + members.back()["encode"] = encode; + + members.back()["memberName"] = member.name; + } + templ("members", members); + if (_options.dynamicInplace) + solAssert(encodingOffset == 0, "In-place encoding should enforce zero head size."); + 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()) + { + solAssert(_to.category() == Type::Category::Array, ""); + Whiskers templ(R"( + function (pos) -> end { + pos := (pos, ) + (pos) + end := add(pos, ) + } + )"); + templ("functionName", functionName); + + // TODO this can make use of CODECOPY for large strings once we have that in Yul + templ("length", to_string(value.size())); + templ("storeLength", arrayStoreLengthForEncodingFunction(dynamic_cast(_to), _options)); + if (_options.padded) + templ("overallSize", to_string(((value.size() + 31) / 32) * 32)); + else + templ("overallSize", to_string(value.size())); + templ("storeLiteralInMemory", m_utils.storeLiteralInMemoryFunction(value)); + 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", formatAsStringOrNumber(value)); + 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", m_utils.combineExternalFunctionIdFunction()) + .render(); + }); + else + return createFunction(functionName, [&]() { + return Whiskers(R"( + function (addr_and_function_id, pos) { + mstore(pos, (addr_and_function_id)) + } + )") + ("functionName", functionName) + ("cleanExtFun", m_utils.cleanupFunction(_to)) + .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)) + { + if (arrayType->dataStoredIn(DataLocation::CallData)) + { + solAssert(!_fromMemory, ""); + return abiDecodingFunctionCalldataArray(*arrayType); + } + else + return abiDecodingFunctionArray(*arrayType, _fromMemory); + } + else if (auto const* structType = dynamic_cast(decodingType)) + { + if (structType->dataStoredIn(DataLocation::CallData)) + { + solAssert(!_fromMemory, ""); + return abiDecodingFunctionCalldataStruct(*structType); + } + else + return abiDecodingFunctionStruct(*structType, _fromMemory); + } + else if (auto const* functionType = dynamic_cast(decodingType)) + return abiDecodingFunctionFunctionType(*functionType, _fromMemory, _forUseOnStack); + else + return abiDecodingFunctionValueType(_type, _fromMemory); +} + +string ABIFunctions::abiDecodingFunctionValueType(Type const& _type, bool _fromMemory) +{ + TypePointer decodingType = _type.decodingType(); + solAssert(decodingType, ""); + solAssert(decodingType->sizeOnStack() == 1, ""); + solAssert(decodingType->isValueType(), ""); + solAssert(!decodingType->isDynamicallyEncoded(), ""); + solAssert(decodingType->calldataEncodedSize() == 32, ""); + + string functionName = + "abi_decode_" + + _type.identifier() + + (_fromMemory ? "_fromMemory" : ""); + return createFunction(functionName, [&]() { + Whiskers templ(R"( + function (offset, end) -> value { + value := (offset) + (value) + } + )"); + templ("functionName", functionName); + templ("load", _fromMemory ? "mload" : "calldataload"); + // Validation should use the type and not decodingType, because e.g. + // the decoding type of an enum is a plain int. + templ("validator", m_utils.validatorFunction(_type, true)); + return templ.render(); + }); + +} + +string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _fromMemory) +{ + solAssert(_type.dataStoredIn(DataLocation::Memory), ""); + + string functionName = + "abi_decode_" + + _type.identifier() + + (_fromMemory ? "_fromMemory" : ""); + + return createFunction(functionName, [&]() { + string load = _fromMemory ? "mload" : "calldataload"; + Whiskers templ( + R"( + // + function (offset, end) -> array { + if iszero(slt(add(offset, 0x1f), end)) { } + let length := + array := (, length, end) + } + )" + ); + // TODO add test + templ("revertString", revertReasonIfDebug("ABI decoding: invalid calldata array offset")); + templ("functionName", functionName); + templ("readableTypeName", _type.toString(true)); + templ("retrieveLength", _type.isDynamicallySized() ? (load + "(offset)") : toCompactHexWithPrefix(_type.length())); + templ("offset", _type.isDynamicallySized() ? "add(offset, 0x20)" : "offset"); + templ("abiDecodeAvailableLen", abiDecodingFunctionArrayAvailableLength(_type, _fromMemory)); + return templ.render(); + }); +} + +string ABIFunctions::abiDecodingFunctionArrayAvailableLength(ArrayType const& _type, bool _fromMemory) +{ + solAssert(_type.dataStoredIn(DataLocation::Memory), ""); + if (_type.isByteArray()) + return abiDecodingFunctionByteArrayAvailableLength(_type, _fromMemory); + + string functionName = + "abi_decode_available_length_" + + _type.identifier() + + (_fromMemory ? "_fromMemory" : ""); + + return createFunction(functionName, [&]() { + Whiskers templ(R"( + // + function (offset, length, end) -> array { + array := ((length)) + let dst := array + + 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("allocate", m_utils.allocationFunction()); + templ("allocationSize", m_utils.arrayAllocationSizeFunction(_type)); + string calldataStride = toCompactHexWithPrefix(_type.calldataStride()); + templ("stride", calldataStride); + if (_type.isDynamicallySized()) + templ("storeLength", "mstore(array, length) dst := add(array, 0x20)"); + else + templ("storeLength", ""); + if (_type.baseType()->isDynamicallyEncoded()) + { + templ("staticBoundsCheck", ""); + string load = _fromMemory ? "mload" : "calldataload"; + templ("retrieveElementPos", "add(offset, " + load + "(src))"); + } + else + { + templ("staticBoundsCheck", "if gt(add(src, mul(length, " + + calldataStride + + ")), end) { " + + revertReasonIfDebug("ABI decoding: invalid calldata array stride") + + " }" + ); + templ("retrieveElementPos", "src"); + } + templ("decodingFun", abiDecodingFunction(*_type.baseType(), _fromMemory, false)); + return templ.render(); + }); +} + +string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type) +{ + solAssert(_type.dataStoredIn(DataLocation::CallData), ""); + if (!_type.isDynamicallySized()) + solAssert(_type.length() < u256("0xffffffffffffffff"), ""); + solAssert(_type.calldataStride() > 0, ""); + solAssert(_type.calldataStride() < u256("0xffffffffffffffff"), ""); + + string functionName = + "abi_decode_" + + _type.identifier(); + return createFunction(functionName, [&]() { + Whiskers w; + if (_type.isDynamicallySized()) + { + w = Whiskers(R"( + // + function (offset, end) -> arrayPos, length { + if iszero(slt(add(offset, 0x1f), end)) { } + length := calldataload(offset) + if gt(length, 0xffffffffffffffff) { } + arrayPos := add(offset, 0x20) + if gt(add(arrayPos, mul(length, )), end) { } + } + )"); + w("revertStringOffset", revertReasonIfDebug("ABI decoding: invalid calldata array offset")); + w("revertStringLength", revertReasonIfDebug("ABI decoding: invalid calldata array length")); + } + else + { + w = Whiskers(R"( + // + function (offset, end) -> arrayPos { + arrayPos := offset + if gt(add(arrayPos, mul(, )), end) { } + } + )"); + w("length", toCompactHexWithPrefix(_type.length())); + } + w("revertStringPos", revertReasonIfDebug("ABI decoding: invalid calldata array stride")); + w("functionName", functionName); + w("readableTypeName", _type.toString(true)); + w("stride", toCompactHexWithPrefix(_type.calldataStride())); + + // TODO add test + return w.render(); + }); +} + +string ABIFunctions::abiDecodingFunctionByteArrayAvailableLength(ArrayType const& _type, bool _fromMemory) +{ + solAssert(_type.dataStoredIn(DataLocation::Memory), ""); + solAssert(_type.isByteArray(), ""); + + string functionName = + "abi_decode_available_length_" + + _type.identifier() + + (_fromMemory ? "_fromMemory" : ""); + + return createFunction(functionName, [&]() { + Whiskers templ(R"( + function (src, length, end) -> array { + array := ((length)) + mstore(array, length) + let dst := add(array, 0x20) + if gt(add(src, length), end) { } + (src, dst, length) + } + )"); + templ("revertStringLength", revertReasonIfDebug("ABI decoding: invalid byte array length")); + templ("functionName", functionName); + templ("allocate", m_utils.allocationFunction()); + templ("allocationSize", m_utils.arrayAllocationSizeFunction(_type)); + templ("copyToMemFun", m_utils.copyToMemoryFunction(!_fromMemory)); + return templ.render(); + }); +} + +string ABIFunctions::abiDecodingFunctionCalldataStruct(StructType const& _type) +{ + solAssert(_type.dataStoredIn(DataLocation::CallData), ""); + string functionName = + "abi_decode_" + + _type.identifier(); + + return createFunction(functionName, [&]() { + Whiskers w{R"( + // + function (offset, end) -> value { + if slt(sub(end, offset), ) { } + value := offset + } + )"}; + // TODO add test + w("revertString", revertReasonIfDebug("ABI decoding: struct calldata too short")); + w("functionName", functionName); + w("readableTypeName", _type.toString(true)); + w("minimumSize", to_string(_type.isDynamicallyEncoded() ? _type.calldataEncodedTailSize() : _type.calldataEncodedSize(true))); + return w.render(); + }); +} + +string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fromMemory) +{ + solAssert(!_type.dataStoredIn(DataLocation::CallData), ""); + string functionName = + "abi_decode_" + + _type.identifier() + + (_fromMemory ? "_fromMemory" : ""); + + return createFunction(functionName, [&]() { + Whiskers templ(R"( + // + function (headStart, end) -> value { + if slt(sub(end, headStart), ) { } + value := () + <#members> + { + // + + } + + } + )"); + // TODO add test + templ("revertString", revertReasonIfDebug("ABI decoding: struct data too short")); + templ("functionName", functionName); + templ("readableTypeName", _type.toString(true)); + templ("allocate", m_utils.allocationFunction()); + solAssert(_type.memoryDataSize() < u256("0xffffffffffffffff"), ""); + templ("memorySize", toCompactHexWithPrefix(_type.memoryDataSize())); + size_t headPos = 0; + vector> members; + for (auto const& member: _type.members(nullptr)) + { + solAssert(member.type, ""); + solAssert(!member.type->containsNestedMapping(), ""); + auto decodingType = member.type->decodingType(); + solAssert(decodingType, ""); + Whiskers memberTempl(R"( + + let offset := (add(headStart, )) + if gt(offset, 0xffffffffffffffff) { } + + let offset := + + mstore(add(value, ), (add(headStart, offset), end)) + )"); + memberTempl("dynamic", decodingType->isDynamicallyEncoded()); + // TODO add test + memberTempl("revertString", revertReasonIfDebug("ABI decoding: invalid struct offset")); + 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.emplace_back(); + members.back()["decode"] = memberTempl.render(); + members.back()["memberName"] = member.name; + headPos += decodingType->calldataHeadSize(); + } + 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, end)) + } + )") + ("functionName", functionName) + ("decodeFun", abiDecodingFunctionFunctionType(_type, _fromMemory, false)) + ("splitExtFun", m_utils.splitExternalFunctionIdFunction()) + .render(); + } + else + { + return Whiskers(R"( + function (offset, end) -> fun { + fun := (offset) + (fun) + } + )") + ("functionName", functionName) + ("load", _fromMemory ? "mload" : "calldataload") + ("validateExtFun", m_utils.validatorFunction(_type, true)) + .render(); + } + }); +} + +string ABIFunctions::calldataAccessFunction(Type const& _type) +{ + solAssert(_type.isValueType() || _type.dataStoredIn(DataLocation::CallData), ""); + string functionName = "calldata_access_" + _type.identifier(); + return createFunction(functionName, [&]() { + if (_type.isDynamicallyEncoded()) + { + unsigned int tailSize = _type.calldataEncodedTailSize(); + solAssert(tailSize > 1, ""); + Whiskers w(R"( + function (base_ref, ptr) -> { + let rel_offset_of_tail := calldataload(ptr) + if iszero(slt(rel_offset_of_tail, sub(sub(calldatasize(), base_ref), sub(, 1)))) { } + value := add(rel_offset_of_tail, base_ref) + + } + )"); + if (_type.isDynamicallySized()) + { + auto const* arrayType = dynamic_cast(&_type); + solAssert(!!arrayType, ""); + w("handleLength", Whiskers(R"( + length := calldataload(value) + value := add(value, 0x20) + if gt(length, 0xffffffffffffffff) { } + if sgt(base_ref, sub(calldatasize(), mul(length, ))) { } + )") + ("calldataStride", toCompactHexWithPrefix(arrayType->calldataStride())) + // TODO add test + ("revertStringLength", revertReasonIfDebug("Invalid calldata access length")) + // TODO add test + ("revertStringStride", revertReasonIfDebug("Invalid calldata access stride")) + .render()); + w("return", "value, length"); + } + else + { + w("handleLength", ""); + w("return", "value"); + } + w("neededLength", toCompactHexWithPrefix(tailSize)); + w("functionName", functionName); + w("revertStringOffset", revertReasonIfDebug("Invalid calldata access offset")); + return w.render(); + } + else if (_type.isValueType()) + { + string decodingFunction; + if (auto const* functionType = dynamic_cast(&_type)) + decodingFunction = abiDecodingFunctionFunctionType(*functionType, false, false); + else + decodingFunction = abiDecodingFunctionValueType(_type, false); + // Note that the second argument to the decoding function should be discarded after inlining. + return Whiskers(R"( + function (baseRef, ptr) -> value { + value := (ptr, add(ptr, 32)) + } + )") + ("functionName", functionName) + ("decodingFunction", decodingFunction) + .render(); + } + else + { + solAssert( + _type.category() == Type::Category::Array || + _type.category() == Type::Category::Struct, + "" + ); + return Whiskers(R"( + function (baseRef, ptr) -> value { + value := ptr + } + )") + ("functionName", functionName) + .render(); + } + }); +} + +string ABIFunctions::arrayStoreLengthForEncodingFunction(ArrayType const& _type, EncodingOptions const& _options) +{ + string functionName = "array_storeLengthForEncoding_" + _type.identifier() + _options.toFunctionNameSuffix(); + return createFunction(functionName, [&]() { + if (_type.isDynamicallySized() && !_options.dynamicInplace) + 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::createFunction(string const& _name, function const& _creator) +{ + return m_functionCollector.createFunction(_name, _creator); +} + +size_t ABIFunctions::headSize(TypePointers const& _targetTypes) +{ + size_t headSize = 0; + for (auto const& t: _targetTypes) + headSize += t->calldataHeadSize(); + + return headSize; +} + +size_t ABIFunctions::numVariablesForType(Type const& _type, EncodingOptions const& _options) +{ + if (_type.category() == Type::Category::Function && !_options.encodeFunctionFromStack) + return 1; + else + return _type.sizeOnStack(); +} + +std::string ABIFunctions::revertReasonIfDebug(std::string const& _message) +{ + return YulUtilFunctions::revertReasonIfDebug(m_revertStrings, _message); +} diff --git a/libsolidity/codegen/ABIFunctions.h b/libsolidity/codegen/ABIFunctions.h new file mode 100644 index 0000000..2c508f3 --- /dev/null +++ b/libsolidity/codegen/ABIFunctions.h @@ -0,0 +1,271 @@ +// SPDX-License-Identifier: GPL-3.0 +/** + * @author Christian + * @date 2017 + * Routines that generate Yul code related to ABI encoding, decoding and type conversions. + */ + +#pragma once + +#include +#include + +#include + +#include + +#include +#include +#include + +namespace solidity::frontend +{ + +class Type; +class ArrayType; +class StructType; +class FunctionType; +using TypePointer = Type const*; +using TypePointers = std::vector; + +/** + * Class to generate encoding and decoding functions. Also maintains a collection + * of "functions to be generated" in order to avoid generating the same function + * multiple times. + * + * Make sure to include the result of ``requestedFunctions()`` to a block that + * is visible from the code that was generated here, or use named labels. + */ +class ABIFunctions +{ +public: + explicit ABIFunctions( + langutil::EVMVersion _evmVersion, + RevertStrings _revertStrings, + MultiUseYulFunctionCollector& _functionCollector + ): + m_evmVersion(_evmVersion), + m_revertStrings(_revertStrings), + m_functionCollector(_functionCollector), + m_utils(_evmVersion, m_revertStrings, m_functionCollector) + {} + + /// @returns name of an assembly function to ABI-encode values of @a _givenTypes + /// into memory, converting the types to @a _targetTypes on the fly. + /// Parameters are: ... , i.e. + /// the layout on the stack is ... with + /// the top of the stack on the right. + /// The values represent stack slots. If a type occupies more or less than one + /// stack slot, it takes exactly that number of values. + /// Returns a pointer to the end of the area written in memory. + /// Does not allocate memory (does not change the free memory pointer), but writes + /// to memory starting at $headStart and an unrestricted amount after that. + /// If @reversed is true, the order of the variables after is reversed. + std::string tupleEncoder( + TypePointers const& _givenTypes, + TypePointers _targetTypes, + bool _encodeAsLibraryTypes = false, + bool _reversed = false + ); + + /// Specialization of tupleEncoder to _reversed = true + std::string tupleEncoderReversed( + TypePointers const& _givenTypes, + TypePointers const& _targetTypes, + bool _encodeAsLibraryTypes = false + ) { + return tupleEncoder(_givenTypes, _targetTypes, _encodeAsLibraryTypes, true); + } + + /// @returns name of an assembly function to encode values of @a _givenTypes + /// with packed encoding into memory, converting the types to @a _targetTypes on the fly. + /// Parameters are: ... , i.e. + /// the layout on the stack is ... with + /// the top of the stack on the right. + /// The values represent stack slots. If a type occupies more or less than one + /// stack slot, it takes exactly that number of values. + /// Returns a pointer to the end of the area written in memory. + /// Does not allocate memory (does not change the free memory pointer), but writes + /// to memory starting at memPos and an unrestricted amount after that. + /// If @reversed is true, the order of the variables after is reversed. + std::string tupleEncoderPacked( + TypePointers const& _givenTypes, + TypePointers _targetTypes, + bool _reversed = false + ); + + /// Specialization of tupleEncoderPacked to _reversed = true + std::string tupleEncoderPackedReversed(TypePointers const& _givenTypes, TypePointers const& _targetTypes) + { + return tupleEncoderPacked(_givenTypes, _targetTypes, true); + } + + /// @returns name of an assembly function to ABI-decode values of @a _types + /// into memory. If @a _fromMemory is true, decodes from memory instead of + /// from calldata. + /// Can allocate memory. + /// Inputs: (layout reversed on stack) + /// Outputs: ... + /// The values represent stack slots. If a type occupies more or less than one + /// stack slot, it takes exactly that number of values. + std::string tupleDecoder(TypePointers const& _types, bool _fromMemory = false); + + struct EncodingOptions + { + /// Pad/signextend value types and bytes/string to multiples of 32 bytes. + /// If false, data is always left-aligned. + /// Note that this is always re-set to true for the elements of arrays and structs. + bool padded = true; + /// Store arrays and structs in place without "data pointer" and do not store the length. + bool dynamicInplace = false; + /// Only for external function types: The value is a pair of address / function id instead + /// of a memory pointer to the compression representation. + bool encodeFunctionFromStack = false; + /// Encode storage pointers as storage pointers (we are targeting a library call). + bool encodeAsLibraryTypes = false; + + /// @returns a string to uniquely identify the encoding options for the encoding + /// function name. Skips everything that has its default value. + std::string toFunctionNameSuffix() const; + }; + + /// Internal encoding function that is also used by some copying routines. + /// @returns the name of the ABI encoding function with the given type + /// and queues the generation of the function to the requested functions. + /// @param _fromStack if false, the input value was just loaded from storage + /// or memory and thus might be compacted into a single slot (depending on the type). + std::string abiEncodingFunction( + Type const& _givenType, + Type const& _targetType, + EncodingOptions const& _options + ); + /// Internal encoding function that is also used by some copying routines. + /// @returns the name of a function that internally calls `abiEncodingFunction` + /// but always returns the updated encoding position, even if the type is + /// statically encoded. + std::string abiEncodeAndReturnUpdatedPosFunction( + Type const& _givenType, + Type const& _targetType, + EncodingOptions const& _options + ); + + /// Decodes array in case of dynamic arrays with offset pointing to + /// data and length already on stack + /// signature: (dataOffset, length, dataEnd) -> decodedArray + std::string abiDecodingFunctionArrayAvailableLength(ArrayType const& _type, bool _fromMemory); + + /// Internal decoding function that is also used by some copying routines. + /// @returns the name of a function that decodes structs. + /// signature: (dataStart, dataEnd) -> decodedStruct + std::string abiDecodingFunctionStruct(StructType const& _type, bool _fromMemory); + +private: + /// Part of @a abiEncodingFunction for array target type and given calldata array. + /// Uses calldatacopy and does not perform cleanup or validation and can therefore only + /// be used for byte arrays and arrays with the base type uint256 or bytes32. + std::string abiEncodingFunctionCalldataArrayWithoutCleanup( + Type const& _givenType, + Type const& _targetType, + EncodingOptions const& _options + ); + /// Part of @a abiEncodingFunction for array target type and given memory array or + /// a given storage array with every item occupies one or multiple full slots. + std::string abiEncodingFunctionSimpleArray( + ArrayType const& _givenType, + ArrayType const& _targetType, + EncodingOptions const& _options + ); + std::string abiEncodingFunctionMemoryByteArray( + ArrayType const& _givenType, + ArrayType const& _targetType, + EncodingOptions const& _options + ); + /// Part of @a abiEncodingFunction for array target type and given storage array + /// where multiple items are packed into the same storage slot. + std::string abiEncodingFunctionCompactStorageArray( + ArrayType const& _givenType, + ArrayType const& _targetType, + EncodingOptions const& _options + ); + + /// Part of @a abiEncodingFunction for struct types. + std::string abiEncodingFunctionStruct( + StructType const& _givenType, + StructType const& _targetType, + EncodingOptions const& _options + ); + + // @returns the name of the ABI encoding function with the given type + // and queues the generation of the function to the requested functions. + // Case for _givenType being a string literal + std::string abiEncodingFunctionStringLiteral( + Type const& _givenType, + Type const& _targetType, + EncodingOptions const& _options + ); + + std::string abiEncodingFunctionFunctionType( + FunctionType const& _from, + Type const& _to, + EncodingOptions const& _options + ); + + /// @returns the name of the ABI decoding function for the given type + /// and queues the generation of the function to the requested functions. + /// The caller has to ensure that no out of bounds access (at least to the static + /// part) can happen inside this function. + /// @param _fromMemory if decoding from memory instead of from calldata + /// @param _forUseOnStack if the decoded value is stored on stack or in memory. + std::string abiDecodingFunction( + Type const& _type, + bool _fromMemory, + bool _forUseOnStack + ); + + /// Part of @a abiDecodingFunction for value types. + std::string abiDecodingFunctionValueType(Type const& _type, bool _fromMemory); + /// Part of @a abiDecodingFunction for "regular" array types. + std::string abiDecodingFunctionArray(ArrayType const& _type, bool _fromMemory); + /// Part of @a abiDecodingFunction for calldata array types. + std::string abiDecodingFunctionCalldataArray(ArrayType const& _type); + /// Part of @a abiDecodingFunctionArrayWithAvailableLength + std::string abiDecodingFunctionByteArrayAvailableLength(ArrayType const& _type, bool _fromMemory); + /// Part of @a abiDecodingFunction for calldata struct types. + std::string abiDecodingFunctionCalldataStruct(StructType const& _type); + /// Part of @a abiDecodingFunction for array types. + std::string abiDecodingFunctionFunctionType(FunctionType const& _type, bool _fromMemory, bool _forUseOnStack); + /// @returns the name of a function that retrieves an element from calldata. + std::string calldataAccessFunction(Type const& _type); + + /// @returns the name of a function used during encoding that stores the length + /// if the array is dynamically sized (and the options do not request in-place encoding). + /// It returns the new encoding position. + /// If the array is not dynamically sized (or in-place encoding was requested), + /// does nothing and just returns the position again. + std::string arrayStoreLengthForEncodingFunction(ArrayType const& _type, EncodingOptions const& _options); + + /// Helper function that uses @a _creator to create a function and add it to + /// @a m_requestedFunctions if it has not been created yet and returns @a _name in both + /// cases. + std::string createFunction(std::string const& _name, std::function const& _creator); + + /// @returns the size of the static part of the encoding of the given types. + static size_t headSize(TypePointers const& _targetTypes); + + /// @returns the number of variables needed to store a type. + /// This is one for almost all types. The exception being dynamically sized calldata arrays or + /// external function types (if we are encoding from stack, i.e. _options.encodeFunctionFromStack + /// is true), for which it is two. + static size_t numVariablesForType(Type const& _type, EncodingOptions const& _options); + + /// @returns code that stores @param _message for revert reason + /// if m_revertStrings is debug. + std::string revertReasonIfDebug(std::string const& _message = ""); + + langutil::EVMVersion m_evmVersion; + RevertStrings const m_revertStrings; + MultiUseYulFunctionCollector& m_functionCollector; + YulUtilFunctions m_utils; +}; + +} diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index 0c60d5b..8914af9 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -242,16 +242,18 @@ unsigned CompilerContext::numberOfLocalVariables() const } // Solidity++: -void CompilerContext::startAwaitCallback(AwaitExpression const& _awaitExpression) +void CompilerContext::addCallbackDest(uint32_t const _callbackId) { - auto tag = m_awaitCallbacks.find(&_awaitExpression); + auto tag = m_awaitCallbacks.find(_callbackId); if (tag == m_awaitCallbacks.end()) { - auto text = _awaitExpression.location().text() + " [id=" + to_string(_awaitExpression.id()) + "]"; + auto text = "callback dest of " + to_string(_callbackId); evmasm::AssemblyItem callbackTag(newTag(text)); - m_awaitCallbacks.insert(make_pair(&_awaitExpression, callbackTag)); + m_awaitCallbacks.insert(make_pair(_callbackId, callbackTag)); debug("Add tag for await: " + callbackTag.toAssemblyText(*m_asm)); *this << callbackTag; + // restore context + *this << Instruction::CALLBACKDEST; } } diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index 7b77386..e480524 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -85,7 +85,7 @@ class CompilerContext unsigned numberOfLocalVariables() const; // Solidity++: - void startAwaitCallback(AwaitExpression const& _awaitExpression); + void addCallbackDest(uint32_t const _callbackId); void setOtherCompilers(std::map> const& _otherCompilers) { m_otherCompilers = _otherCompilers; } std::shared_ptr compiledContract(ContractDefinition const& _contract) const; @@ -297,7 +297,7 @@ class CompilerContext RevertStrings revertStrings() const { return m_revertStrings; } // Solidity++ - std::map awaitCallbacks() const { return m_awaitCallbacks; } + std::map awaitCallbacks() const { return m_awaitCallbacks; } /// Solidity++: output debug info in verbose mode void debug(std::string info) const { if (m_verbose) std::clog << " [CompilerContext] " << info << std::endl; } @@ -374,7 +374,7 @@ class CompilerContext std::map m_lowLevelFunctions; /// Solidity++: An index of await callback labels - std::map m_awaitCallbacks; + std::map m_awaitCallbacks; /// Collector for yul functions. MultiUseYulFunctionCollector m_yulFunctionCollector; diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index a1b2e33..3ec6995 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -56,17 +56,18 @@ namespace class StackHeightChecker { public: - explicit StackHeightChecker(CompilerContext const& _context): - m_context(_context), stackHeight(m_context.stackHeight()) {} + explicit StackHeightChecker(CompilerContext const& _context, string const& _description): + m_context(_context), m_description(_description), stackHeight(m_context.stackHeight()) {} void check() { solAssert( m_context.stackHeight() == stackHeight, - std::string("I sense a disturbance in the stack: ") + to_string(m_context.stackHeight()) + " vs " + to_string(stackHeight) + std::string("I sense a disturbance in the stack: ") + to_string(m_context.stackHeight()) + " vs " + to_string(stackHeight) + " \nin " + m_description ); } private: CompilerContext const& m_context; + string const& m_description; unsigned stackHeight; }; @@ -101,8 +102,11 @@ void ContractCompiler::compileContract( // Solidity++: Will append function selector after compiling await expressions - debug("Jump to function selector " + m_functionSelectorTag.toAssemblyText(m_context.assembly())); - m_context.appendJumpTo(m_functionSelectorTag); + if (!_contract.isLibrary()) // Solidity++: Libraries don't have function selectors + { + debug("Jump to function selector " + m_functionSelectorTag.toAssemblyText(m_context.assembly())); + m_context.appendJumpTo(m_functionSelectorTag); + } debug("Compiled."); } @@ -195,8 +199,8 @@ void ContractCompiler::appendInitAndConstructorCode(ContractDefinition const& _c appendConstructor(*constructor); else { - // Implicit constructors are always non-payable. - appendCallValueCheck(); + // Solidity++: Implicit constructors are always payable. +// appendCallValueCheck(); if (auto c = _contract.nextConstructor(m_context.mostDerivedContract())) appendBaseConstructor(*c); } @@ -220,10 +224,9 @@ size_t ContractCompiler::packIntoContractCreator(ContractDefinition const& _cont m_runtimeCompiler->appendMissingFunctions(); m_runtimeCompiler->appendFunctionSelector(_contract); - // Solidity++: Moved from appendMissingFunctions() - m_runtimeCompiler->m_context.appendYulUtilityFunctions(m_optimiserSettings); // Solidity++: Moved from appendMissingFunctions() + m_runtimeCompiler->m_context.appendYulUtilityFunctions(m_optimiserSettings); m_context.appendYulUtilityFunctions(m_optimiserSettings); CompilerContext::LocationSetter locationSetter(m_context, _contract); @@ -266,6 +269,10 @@ size_t ContractCompiler::deployLibrary(ContractDefinition const& _contract) appendMissingFunctions(); m_runtimeCompiler->appendMissingFunctions(); + // Solidity++: Moved from appendMissingFunctions() + m_runtimeCompiler->m_context.appendYulUtilityFunctions(m_optimiserSettings); + m_context.appendYulUtilityFunctions(m_optimiserSettings); + CompilerContext::LocationSetter locationSetter(m_context, _contract); solAssert(m_context.runtimeSub() != numeric_limits::max(), "Runtime sub not registered"); @@ -390,40 +397,24 @@ void ContractCompiler::appendInternalSelector( // Start with some comparisons to avoid overflow, then do the actual comparison. + // Solidity++: remove binary search functionality debug("Append internal function selector: notFoundTag=" + _notFoundTag.toAssemblyText(m_context.assembly()) + ", runs=" + toString(_runs)); m_context.appendDebugInfo("ContractCompiler::appendInternalSelector()"); - bool split = false; - if (_ids.size() <= 4) - split = false; - else if (_runs > (17 * evmasm::GasCosts::createDataGas) / 6) - split = true; - else - split = (_runs * 6 * (_ids.size() - 4) > 17 * evmasm::GasCosts::createDataGas); - - if (split) - { - size_t pivotIndex = _ids.size() / 2; - FixedHash<4> pivot{_ids.at(pivotIndex)}; - m_context << dupInstruction(1) << u256(FixedHash<4>::Arith(pivot)) << Instruction::GT; - evmasm::AssemblyItem lessTag{m_context.appendConditionalJump(pivot.hex())}; - // Here, we have funid >= pivot - vector> larger{_ids.begin() + static_cast(pivotIndex), _ids.end()}; - appendInternalSelector(_entryPoints, larger, _notFoundTag, _runs); - m_context << lessTag; - // Here, we have funid < pivot - vector> smaller{_ids.begin(), _ids.begin() + static_cast(pivotIndex)}; - appendInternalSelector(_entryPoints, smaller, _notFoundTag, _runs); - } - else - { - for (auto const& id: _ids) - { - m_context << dupInstruction(1) << u256(FixedHash<4>::Arith(id)) << Instruction::EQ; - m_context.appendConditionalJumpTo(_entryPoints.at(id)); - } - m_context.appendJumpTo(_notFoundTag); - } + m_context.appendDebugInfo("function selector"); + for (auto const& id: _ids) + { + m_context << dupInstruction(1) << u256(FixedHash<4>::Arith(id)) << Instruction::EQ; + m_context.appendConditionalJumpTo(_entryPoints.at(id)); + } + m_context.appendDebugInfo("callback selector"); + for (auto const& entry: m_context.awaitCallbacks()) + { + m_context << dupInstruction(1) << u256(FixedHash<4>::Arith(entry.first)) << Instruction::EQ; + m_context.appendConditionalJumpTo(entry.second); + } + m_context.appendDebugInfo("not found"); + m_context.appendJumpTo(_notFoundTag); m_context.appendDebugInfo("end of ContractCompiler::appendInternalSelector()"); } @@ -459,7 +450,7 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac solAssert(m_context.stackHeight() == 1, "CALL / DELEGATECALL flag expected."); } - if (!_isOffchain) + if (!_isOffchain && !_contract.isLibrary()) // Solidity++: Libraries don't have function selectors { debug("Append function selector " + m_functionSelectorTag.toAssemblyText(m_context.assembly())); m_context << m_functionSelectorTag; @@ -471,12 +462,13 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac FunctionDefinition const* etherReceiver = _contract.receiveFunction(); solAssert(!_contract.isLibrary() || !etherReceiver, "Libraries can't have ether receiver functions"); - bool needToAddCallvalueCheck = true; - if (!hasPayableFunctions(_contract) && !interfaceFunctions.empty() && !_contract.isLibrary()) - { - appendCallValueCheck(); - needToAddCallvalueCheck = false; - } + // Solidity++: Do not check callvalue by default + bool needToAddCallvalueCheck = false; +// if (!hasPayableFunctions(_contract) && !interfaceFunctions.empty() && !_contract.isLibrary()) +// { +// appendCallValueCheck(); +// needToAddCallvalueCheck = false; +// } evmasm::AssemblyItem notFoundOrReceiveEther = m_context.newTag("notFoundOrReceiveEther"); debug("Tag of notFoundOrReceiveEther is " + notFoundOrReceiveEther.toAssemblyText(m_context.assembly())); @@ -508,25 +500,15 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac debug(" - For interface function: " + it.second->toString(false) + ": " + it.first.hex() + " -> " + tag.toAssemblyText(m_context.assembly())); } - // Solidity++: Callback functions - for (auto const& it: m_context.awaitCallbacks()) - { - auto id = it.first->id(); - auto selector = selectorFromAwaitId(id); - string desc = "calldata unpacker of callback #" + to_string(id); - auto tag = m_context.newTag(desc); - callDataUnpackerEntryPoints.emplace(selector, tag); - sortedIDs.emplace_back(selector); - } - std::sort(sortedIDs.begin(), sortedIDs.end()); appendInternalSelector(callDataUnpackerEntryPoints, sortedIDs, notFound, m_optimiserSettings.expectedExecutionsPerDeployment); } + // Default code for an unrecognized call m_context << notFoundOrReceiveEther; if (!fallback && !etherReceiver) - m_context.appendRevert("Contract does not have fallback nor receive functions"); + m_context << Instruction::STOP; // Solidity++: Receive the token sent by default, rather than reverting the call. else { if (etherReceiver) @@ -574,7 +556,6 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac // Calldata unpacker of interface functions for (auto const& it: interfaceFunctions) { - m_context.setStackOffset(1); FunctionTypePointer const& functionType = it.second; solAssert(functionType->hasDeclaration(), ""); CompilerContext::LocationSetter locationSetter(m_context, functionType->declaration()); @@ -589,7 +570,7 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac m_context << dupInstruction(2); m_context.appendConditionalRevert(false, "Non-view function of library called without DELEGATECALL"); } - m_context.setStackOffset(0); + // We have to allow this for libraries, because value of the previous // call is still visible in the delegatecall. if (!functionType->isPayable() && !_contract.isLibrary() && needToAddCallvalueCheck) @@ -622,151 +603,43 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac // Push return tag debug("Start return tag " + returnTag.toAssemblyText(m_context.assembly())); m_context << returnTag; - // Return tag and input parameters get consumed. + // Return tag and input parameters get consumed after executing the function body m_context.adjustStackOffset( static_cast(CompilerUtils::sizeOnStack(functionType->returnParameterTypes())) - static_cast(CompilerUtils::sizeOnStack(functionType->parameterTypes())) - 1 ); // Consumes the return parameters. - appendReturnValuePacker(functionType, _contract.isLibrary()); + appendReturnValuePacker(functionType->returnParameterTypes(), _contract.isLibrary()); } - // Solidity++: Calldata unpacker of callback functions + // Solidity++: entry to callback functions for (auto const& it: m_context.awaitCallbacks()) { - if (auto call = dynamic_cast(&it.first->expression())) - { - if (auto callType = dynamic_cast(call->expression().annotation().type)) - { - FunctionTypePointer const& callbackType = TypeProvider::callbackFromFunctionCall(callType); - - debug("Generate callback of " + it.first->expression().location().text() + " -> " + callbackType->toString(false)); - CompilerContext::LocationSetter locationSetter(m_context, *it.first); - FixedHash<4> selector = FixedHash<4>::Arith(selectorFromAwaitId(it.first->id())); - auto tag = callDataUnpackerEntryPoints.at(selector); - - debug("Append calldata unpacker: " + tag.toAssemblyText(m_context.assembly())); - m_context << tag; - - // @todo: callvalue check for callbacks -// if (!callbackType->isPayable()) -// appendCallValueCheck(); - - // Solidity: Tags of function declarations DOES exist here, we have pushed them into compilation queue in compileContract() - auto tagContinuation = it.second; - - // Append jump to function declaration - debug("Jump to await continuation: " + tagContinuation.toAssemblyText(m_context.assembly())); - m_context.appendDebugInfo("jump to await continuation"); - m_context.appendJumpTo( - tagContinuation, - evmasm::AssemblyItem::JumpType::IntoFunction + auto tagContinuation = it.second; + debug("Jump to await continuation: " + tagContinuation.toAssemblyText(m_context.assembly())); + m_context.appendDebugInfo("jump to await continuation"); + m_context.appendJumpTo( + tagContinuation, + evmasm::AssemblyItem::JumpType::IntoFunction ); - } - } } } -// Solidity++: redefine this function to process return for asyn functions -void ContractCompiler::appendReturnValuePacker(FunctionTypePointer const& _functionType, bool _isLibrary) +void ContractCompiler::appendReturnValuePacker(TypePointers const& _typeParameters, bool _isLibrary) { - m_context.appendDebugInfo("ContractCompiler::appendReturnValuePacker()"); - auto _returnParameters = _functionType->returnParameterTypes(); - CompilerUtils utils(m_context); - if (_functionType->executionBehavior() == ExecutionBehavior::Async) // handle async functions + CompilerUtils utils(m_context); + if (_typeParameters.empty()) + m_context << Instruction::STOP; + else { - // send message call to the sender - // stack: [ret_0, ..., ret_n] top - // load callback id - auto tagNoSendingCallback = m_context.newTag("no sending callback"); - - unsigned callbackOffset = 4 + CompilerUtils(m_context).sizeOnCalldata(_functionType->parameterTypes()); - m_context.appendDebugInfo("check calldata length, if callback_id is not included, jump to the end"); - m_context << Instruction::CALLDATASIZE; - m_context << (callbackOffset + 4); - m_context << Instruction::GT; - m_context.appendConditionalJumpTo(tagNoSendingCallback); - - m_context << callbackOffset; - CompilerUtils(m_context).loadFromMemoryDynamic(IntegerType(32), true, false, false); - // stack: [ret_0, ..., ret_n, callback] top - - m_context << Instruction::DUP1; - m_context << Instruction::ISZERO; - // if callback id is 0, do not send callback message - m_context.appendConditionalJumpTo(tagNoSendingCallback); - - // Copy callback identifier to memory. - m_context.appendDebugInfo("copy callback identifier to memory"); utils.fetchFreeMemoryPointer(); - // stack: [ret_0, ..., ret_n, callback, mem_start] top - m_context << Instruction::SWAP1; - // stack: [ret_0, ..., ret_n, mem_start, callback] top - CompilerUtils(m_context).storeInMemoryDynamic(IntegerType(32), false); - // stack: [ret_0, ..., ret_n, mem_start_new] top - - // Move arguments to memory, will not update the free memory pointer (but will update the memory - // pointer on the stack). - m_context.appendDebugInfo("move arguments to memory"); - utils.abiEncode(_returnParameters, _returnParameters, _isLibrary); - - m_context.appendDebugInfo("stack: [mem_end] top"); - - // Append context to calldata of callback - // calldata of caller: [function_id, params, callback_id, context] - unsigned contextOffset = 4 + CompilerUtils::sizeOnCalldata(_functionType->parameterTypes()) + 4; - - m_context << Instruction::DUP1; - m_context << contextOffset; - m_context << Instruction::DUP1; - // stack: [input_end, input_end, context_data_offset, context_data_offset] top - - // context_size = calldata_size - context_offset - m_context << Instruction::CALLDATASIZE; - m_context << Instruction::SUB; - m_context.appendDebugInfo("stack: [input_end, input_end, context_data_offset, context_size] top"); - m_context << Instruction::DUP1; - // stack: [input_end, input_end, context_data_offset, context_size, context_size] top - utils.moveIntoStack(3); - // stack: [input_end, context_size, input_end, context_data_offset, context_size] top - m_context << Instruction::SWAP2; - // stack: [input_end, context_size, context_size, context_data_offset, input_end(dest_offset)] top - - m_context << Instruction::CALLDATACOPY; - // stack: [input_end, context_size] top - m_context << Instruction::ADD; - - m_context.appendDebugInfo("stack: [input_end_new] top"); - utils.toSizeAfterFreeMemoryPointer(); // calculate size - m_context.appendDebugInfo("stack: [input_size, input_start] top"); - - // push token and amount - m_context.appendDebugInfo("no value to send (0 VITE)"); - m_context << u256(0) << u256("0x2445f6e5cde8c2c70e44"); - // address to send - m_context.appendDebugInfo("stack: [input_size, input_start, amount, token] top"); - m_context << Instruction::CALLER; - m_context.appendDebugInfo("stack: [input_size, input_start, amount, token, callback_address] top"); - // do CALL - m_context << Instruction::CALL; - - // if callback id is 0, jump here: - m_context << tagNoSendingCallback; - m_context << Instruction::STOP; + //@todo optimization: if we return a single memory array, there should be enough space before + // its data to add the needed parts and we avoid a memory copy. + utils.abiEncode(_typeParameters, _typeParameters, _isLibrary); + utils.toSizeAfterFreeMemoryPointer(); + m_context << Instruction::RETURN; } - else if (_returnParameters.empty()) - m_context << Instruction::STOP; - else - { - utils.fetchFreeMemoryPointer(); - //@todo optimization: if we return a single memory array, there should be enough space before - // its data to add the needed parts and we avoid a memory copy. - utils.abiEncode(_returnParameters, _returnParameters, _isLibrary); - utils.toSizeAfterFreeMemoryPointer(); - m_context << Instruction::RETURN; - } - m_context.appendDebugInfo("end of ContractCompiler::appendReturnValuePacker()"); } void ContractCompiler::registerStateVariables(ContractDefinition const& _contract) @@ -886,7 +759,7 @@ bool ContractCompiler::visit(FunctionDefinition const& _function) BOOST_THROW_EXCEPTION( StackTooDeepError() << errinfo_sourceLocation(_function.location()) << - errinfo_comment("Stack too deep, try removing local variables.") + errinfo_comment("Stack too deep (" + to_string(stackLayout.size()) + " > 17), try removing local variables.") ); while (!stackLayout.empty() && stackLayout.back() != static_cast(stackLayout.size() - 1)) @@ -1057,7 +930,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly) BOOST_THROW_EXCEPTION( StackTooDeepError() << errinfo_sourceLocation(_inlineAssembly.location()) << - errinfo_comment("Stack too deep, try removing local variables.") + errinfo_comment("Stack too deep (" + to_string(stackDiff) + " not in (1, 16)), try removing local variables.") ); _assembly.appendInstruction(dupInstruction(stackDiff)); } @@ -1092,7 +965,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly) BOOST_THROW_EXCEPTION( StackTooDeepError() << errinfo_sourceLocation(_inlineAssembly.location()) << - errinfo_comment("Stack too deep(" + to_string(stackDiff) + "), try removing local variables.") + errinfo_comment("Stack too deep (" + to_string(stackDiff) + " not in (1, 16)), try removing local variables.") ); } else if (variable->type()->dataStoredIn(DataLocation::CallData)) @@ -1156,7 +1029,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly) bool ContractCompiler::visit(TryStatement const& _tryStatement) { - StackHeightChecker checker(m_context); + StackHeightChecker checker(m_context, "TryStatement"); CompilerContext::LocationSetter locationSetter(m_context, _tryStatement); compileExpression(_tryStatement.externalCall()); @@ -1344,7 +1217,7 @@ bool ContractCompiler::visit(TryCatchClause const& _clause) bool ContractCompiler::visit(IfStatement const& _ifStatement) { - StackHeightChecker checker(m_context); + StackHeightChecker checker(m_context, "IfStatement"); CompilerContext::LocationSetter locationSetter(m_context, _ifStatement); compileExpression(_ifStatement.condition()); m_context << Instruction::ISZERO; @@ -1365,7 +1238,7 @@ bool ContractCompiler::visit(IfStatement const& _ifStatement) bool ContractCompiler::visit(WhileStatement const& _whileStatement) { - StackHeightChecker checker(m_context); + StackHeightChecker checker(m_context, "WhileStatement"); CompilerContext::LocationSetter locationSetter(m_context, _whileStatement); evmasm::AssemblyItem loopStart = m_context.newTag(); @@ -1408,7 +1281,7 @@ bool ContractCompiler::visit(WhileStatement const& _whileStatement) bool ContractCompiler::visit(ForStatement const& _forStatement) { - StackHeightChecker checker(m_context); + StackHeightChecker checker(m_context, "ForStatement"); CompilerContext::LocationSetter locationSetter(m_context, _forStatement); evmasm::AssemblyItem loopStart = m_context.newTag(); evmasm::AssemblyItem loopEnd = m_context.newTag(); @@ -1508,7 +1381,7 @@ bool ContractCompiler::visit(Throw const&) bool ContractCompiler::visit(EmitStatement const& _emit) { CompilerContext::LocationSetter locationSetter(m_context, _emit); - StackHeightChecker checker(m_context); + StackHeightChecker checker(m_context, "EmitStatement"); compileExpression(_emit.eventCall()); checker.check(); return false; @@ -1518,7 +1391,7 @@ bool ContractCompiler::visit(EmitStatement const& _emit) bool ContractCompiler::visit(SendStatement const& _send) { CompilerContext::LocationSetter locationSetter(m_context, _send); - StackHeightChecker checker(m_context); + StackHeightChecker checker(m_context, "SendStatement"); compileExpression(_send.address()); CompilerUtils(m_context).convertType(*_send.address().annotation().type, IntegerType(168), true); compileExpression(_send.expression()); @@ -1536,7 +1409,7 @@ bool ContractCompiler::visit(VariableDeclarationStatement const& _variableDeclar if (decl) appendStackVariableInitialisation(*decl, !_variableDeclarationStatement.initialValue()); - StackHeightChecker checker(m_context); + StackHeightChecker checker(m_context, "VariableDeclarationStatement"); if (Expression const* expression = _variableDeclarationStatement.initialValue()) { CompilerUtils utils(m_context); @@ -1567,7 +1440,8 @@ bool ContractCompiler::visit(VariableDeclarationStatement const& _variableDeclar bool ContractCompiler::visit(ExpressionStatement const& _expressionStatement) { - StackHeightChecker checker(m_context); + auto desc = "ExpressionStatement " + _expressionStatement.location().text(); + StackHeightChecker checker(m_context, desc); CompilerContext::LocationSetter locationSetter(m_context, _expressionStatement); Expression const& expression = _expressionStatement.expression(); compileExpression(expression); @@ -1578,7 +1452,7 @@ bool ContractCompiler::visit(ExpressionStatement const& _expressionStatement) bool ContractCompiler::visit(PlaceholderStatement const& _placeholderStatement) { - StackHeightChecker checker(m_context); + StackHeightChecker checker(m_context, "PlaceholderStatement"); CompilerContext::LocationSetter locationSetter(m_context, _placeholderStatement); solAssert(m_context.arithmetic() == Arithmetic::Checked, "Placeholder cannot be used inside checked block."); appendModifierOrFunctionCode(); diff --git a/libsolidity/codegen/ContractCompiler.h b/libsolidity/codegen/ContractCompiler.h index 50100af..39d16a6 100644 --- a/libsolidity/codegen/ContractCompiler.h +++ b/libsolidity/codegen/ContractCompiler.h @@ -95,7 +95,7 @@ class ContractCompiler: private ASTConstVisitor ); void appendFunctionSelector(ContractDefinition const& _contract, bool _isOffchain = false); void appendCallValueCheck(); - void appendReturnValuePacker(FunctionTypePointer const& _functionType, bool _isLibrary); + void appendReturnValuePacker(TypePointers const& _typeParameters, bool _isLibrary); void registerStateVariables(ContractDefinition const& _contract); void registerImmutableVariables(ContractDefinition const& _contract); diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 4dcf94f..0e3a7dc 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -2136,49 +2136,19 @@ bool ExpressionCompiler::visit(AwaitExpression const& _awaitExpression) CompilerContext::LocationSetter locationSetter(m_context, _awaitExpression); _awaitExpression.expression().accept(*this); - - auto stackHeight = m_context.stackHeight(); - - // Halt the execution after await expression and return nothing - m_context << Instruction::STOP; - auto call = dynamic_cast(&_awaitExpression.expression()); auto callType = dynamic_cast(call->expression().annotation().type); solAssert(callType, "fail to convert function call: " + callType->toString(false)); - // Set callback entry to the continuation code. - m_context.startAwaitCallback(_awaitExpression); - - // Load context from calldata of callback - // callback calldata layout: [callback_id, return_params, context] - unsigned offset = CompilerUtils::dataStartOffset + utils().sizeOnCalldata(callType->returnParameterTypes()); - - m_context << offset; - // stack: [offset] top - auto tagLoadContext = m_context.newTag("load context"); - m_context << tagLoadContext; - utils().loadFromMemoryDynamic(*TypeProvider::uint256(), true); - // stack: [context..., offset_new] top - // while (offset_new < calldata_size) - m_context << Instruction::DUP1 << Instruction::CALLDATASIZE << Instruction::GT; - m_context.appendConditionalJumpTo(tagLoadContext); // continue - // stack: [context..., context_n, offset_new] top - m_context << Instruction::POP; - // stack: [context..., context_n] top - - // Load return values from calldata + // Unpack return values from calldata if (!callType->returnParameterTypes().empty()) { m_context << CompilerUtils::dataStartOffset; m_context << Instruction::DUP1 << Instruction::CALLDATASIZE << Instruction::SUB; - // stack: [context..., context_n, return_offset, return_size] top + CompilerUtils(m_context).abiDecode(callType->returnParameterTypes()); - // stack: [context..., context_n, return_1..., return_n] top } - // resume the stack deposit as if it is never interrupted - m_context.setStackOffset((int)stackHeight); - return false; } @@ -2626,8 +2596,6 @@ void ExpressionCompiler::appendExternalFunctionCall( // } // } - // Copy function identifier to memory. - m_context.appendDebugInfo("copy function identifier to memory."); utils().fetchFreeMemoryPointer(); if (!_functionType.isBareCall()) { @@ -2654,30 +2622,6 @@ void ExpressionCompiler::appendExternalFunctionCall( encodeForLibraryCall ); - // Solidity++: copy callback selector and context to memory - if(!!_callbackSelector) - { - m_context.appendDebugInfo("push callback selector: " + toHex(_callbackSelector, HexPrefix::Add)); - m_context << _callbackSelector; - utils().storeInMemoryDynamic(IntegerType(32), false); // will update memory pointer - // stack: [context_1, ..., context_n, address, function_id, (token), (amount), input_memory_end] top - unsigned contextOffset = (_functionType.valueSet() ? 2 : 0) + 3; - unsigned contextSize = m_context.stackHeight() - contextOffset; - // @todo: improve context packing - solAssert(contextSize >= 0 && contextSize < 12, "Bad context size: " + to_string(contextSize)); - - for(unsigned i = 1; i <= contextSize; i++) { - // fetch the i-th item of context - auto depth = contextOffset + contextSize + 1 - i; - m_context << dupInstruction(depth); - // stack: [context_1, ..., context_n, address, function_id, (token), (amount), input_memory_end, context_i] top - - // append item_i of context to memory - utils().storeInMemoryDynamic(IntegerType(256)); - // stack: [context_1, ..., context_n, address, function_id, (token), (amount), new_input_memory_end] top - } - } - // Solidity++: // Stack now: [address, function_id, (token), (amount), input_memory_end] top @@ -2739,6 +2683,12 @@ void ExpressionCompiler::appendExternalFunctionCall( m_context.appendDebugInfo("contract address"); m_context << dupInstruction(m_context.baseToCurrentStackOffset(contractStackPos)); + if(!!_callbackSelector) + { + m_context.appendDebugInfo("callback id"); + m_context << _callbackSelector; + } + // Solidity++: disable contract existence checking @@ -2776,6 +2726,11 @@ void ExpressionCompiler::appendExternalFunctionCall( m_context << Instruction::DELEGATECALL; else if (useStaticCall) m_context << Instruction::STATICCALL; + else if (!!_callbackSelector) + { + m_context << Instruction::SYNCCALL; + m_context.addCallbackDest(_callbackSelector); + } else m_context << Instruction::CALL; @@ -2833,49 +2788,15 @@ void ExpressionCompiler::appendExternalFunctionCall( m_context << Instruction::SUB << Instruction::MLOAD; } - // Solidity++: no real return data, but need placeholder - for(auto returnType : _functionType.returnParameterTypes()) { - utils().pushZeroValue(*returnType); // just a placeholder, do not use it + // Solidity++: no real return data, but need placeholder in async call (call external function without await operator) + // r = a.f(); // r will be set to zero value of return type of f(). + if (!_callbackSelector) + { + for(auto returnType : _functionType.returnParameterTypes()) { + utils().pushZeroValue(*returnType); // just a placeholder, do not use it + } } - // else if (!returnTypes.empty()) - // { - // utils().fetchFreeMemoryPointer(); - // // Stack: return_data_start - - // // The old decoder did not allocate any memory (i.e. did not touch the free - // // memory pointer), but kept references to the return data for - // // (statically-sized) arrays - // bool needToUpdateFreeMemoryPtr = false; - // if (dynamicReturnSize || m_context.useABICoderV2()) - // needToUpdateFreeMemoryPtr = true; - // else - // for (auto const& retType: returnTypes) - // if (dynamic_cast(retType)) - // needToUpdateFreeMemoryPtr = true; - - // // Stack: return_data_start - // if (dynamicReturnSize) - // { - // solAssert(haveReturndatacopy, ""); - // m_context.appendInlineAssembly("{ returndatacopy(return_data_start, 0, returndatasize()) }", {"return_data_start"}); - // } - // else - // solAssert(retSize > 0, ""); - // // Always use the actual return length, and not our calculated expected length, if returndatacopy is supported. - // // This ensures it can catch badly formatted input from external calls. - // m_context << (haveReturndatacopy ? evmasm::AssemblyItem(Instruction::RETURNDATASIZE) : u256(retSize)); - // // Stack: return_data_start return_data_size - // if (needToUpdateFreeMemoryPtr) - // m_context.appendInlineAssembly(R"({ - // // round size to the next multiple of 32 - // let newMem := add(start, and(add(size, 0x1f), not(0x1f))) - // mstore(0x40, newMem) - // })", {"start", "size"}); - - // utils().abiDecode(returnTypes, true); - // } - if (_tryCall) { // Success branch will reach this, failure branch will directly jump to endTag.