diff --git a/Changelog.md b/Changelog.md index a53c6a858..9bde3fffd 100644 --- a/Changelog.md +++ b/Changelog.md @@ -7,6 +7,7 @@ Language Features: Compiler Features: * Code Generator: Use ``calldatacopy`` instead of ``codecopy`` to zero out memory past input. * AST: Add a new node for doxygen-style, structured documentation that can be received by contract, function, event and modifier definitions. + * Debug: Provide reason strings for compiler-generated internal reverts when using the ``--revert-strings`` option or the ``settings.debug.revertStrings`` setting on ``debug`` mode. Bugfixes: diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst index fcb611f60..eb4ddf0c6 100644 --- a/docs/using-the-compiler.rst +++ b/docs/using-the-compiler.rst @@ -244,7 +244,7 @@ Input Description // "default", "strip", "debug" and "verboseDebug". // "default" does not inject compiler-generated revert strings and keeps user-supplied ones. // "strip" removes all revert strings (if possible, i.e. if literals are used) keeping side-effects - // "debug" injects strings for compiler-generated internal reverts (not yet implemented) + // "debug" injects strings for compiler-generated internal reverts, implemented for ABI encoders V1 and V2 for now. // "verboseDebug" even appends further information to user-supplied revert strings (not yet implemented) "revertStrings": "default" } diff --git a/libsolidity/codegen/ABIFunctions.cpp b/libsolidity/codegen/ABIFunctions.cpp index df8cacf86..0ad044a54 100644 --- a/libsolidity/codegen/ABIFunctions.cpp +++ b/libsolidity/codegen/ABIFunctions.cpp @@ -180,11 +180,12 @@ string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory) Whiskers templ(R"( function (headStart, dataEnd) { - if slt(sub(dataEnd, headStart), ) { revert(0, 0) } + 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; @@ -211,7 +212,7 @@ string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory) R"( { let offset := (add(headStart, )) - if gt(offset, 0xffffffffffffffff) { revert(0, 0) } + if gt(offset, 0xffffffffffffffff) { } := (add(headStart, offset), dataEnd) } )" : @@ -222,6 +223,8 @@ string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory) } )" ); + // 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)); @@ -453,12 +456,14 @@ string ABIFunctions::abiEncodingFunctionCalldataArrayWithoutCleanup( else templ("scaleLengthByStride", Whiskers(R"( - if gt(length, ) { revert(0, 0) } + 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)); @@ -1124,7 +1129,7 @@ string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _from R"( // function (offset, end) -> array { - if iszero(slt(add(offset, 0x1f), end)) { revert(0, 0) } + if iszero(slt(add(offset, 0x1f), end)) { } let length := array := ((length)) let dst := array @@ -1141,6 +1146,8 @@ string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _from } )" ); + // TODO add test + templ("revertString", revertReasonIfDebug("ABI decoding: invalid calldata array offset")); templ("functionName", functionName); templ("readableTypeName", _type.toString(true)); templ("retrieveLength", !_type.isDynamicallySized() ? toCompactHexWithPrefix(_type.length()) : load + "(offset)"); @@ -1159,7 +1166,12 @@ string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _from } else { - templ("staticBoundsCheck", "if gt(add(src, mul(length, " + calldataStride + ")), end) { revert(0, 0) }"); + 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)); @@ -1184,11 +1196,11 @@ string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type) templ = R"( // function (offset, end) -> arrayPos, length { - if iszero(slt(add(offset, 0x1f), end)) { revert(0, 0) } + if iszero(slt(add(offset, 0x1f), end)) { } length := calldataload(offset) - if gt(length, 0xffffffffffffffff) { revert(0, 0) } + if gt(length, 0xffffffffffffffff) { } arrayPos := add(offset, 0x20) - if gt(add(arrayPos, mul(length, )), end) { revert(0, 0) } + if gt(add(arrayPos, mul(length, )), end) { } } )"; else @@ -1196,10 +1208,14 @@ string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type) // function (offset, end) -> arrayPos { arrayPos := offset - if gt(add(arrayPos, mul(, )), end) { revert(0, 0) } + if gt(add(arrayPos, mul(, )), end) { } } )"; Whiskers w{templ}; + // TODO add test + w("revertStringOffset", revertReasonIfDebug("ABI decoding: invalid calldata array offset")); + w("revertStringLength", revertReasonIfDebug("ABI decoding: invalid calldata array length")); + w("revertStringPos", revertReasonIfDebug("ABI decoding: invalid calldata array stride")); w("functionName", functionName); w("readableTypeName", _type.toString(true)); w("stride", toCompactHexWithPrefix(_type.calldataStride())); @@ -1223,17 +1239,20 @@ string ABIFunctions::abiDecodingFunctionByteArray(ArrayType const& _type, bool _ Whiskers templ( R"( function (offset, end) -> array { - if iszero(slt(add(offset, 0x1f), end)) { revert(0, 0) } + if iszero(slt(add(offset, 0x1f), end)) { } 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) } + if gt(add(src, length), end) { } (src, dst, length) } )" ); + // TODO add test + templ("revertStringOffset", revertReasonIfDebug("ABI decoding: invalid byte array offset")); + templ("revertStringLength", revertReasonIfDebug("ABI decoding: invalid byte array length")); templ("functionName", functionName); templ("load", _fromMemory ? "mload" : "calldataload"); templ("allocate", m_utils.allocationFunction()); @@ -1254,10 +1273,12 @@ string ABIFunctions::abiDecodingFunctionCalldataStruct(StructType const& _type) Whiskers w{R"( // function (offset, end) -> value { - if slt(sub(end, offset), ) { revert(0, 0) } + 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))); @@ -1277,7 +1298,7 @@ string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fr Whiskers templ(R"( // function (headStart, end) -> value { - if slt(sub(end, headStart), ) { revert(0, 0) } + if slt(sub(end, headStart), ) { } value := () <#members> { @@ -1287,6 +1308,8 @@ string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fr } )"); + // 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()); @@ -1305,7 +1328,7 @@ string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fr dynamic ? R"( let offset := (add(headStart, )) - if gt(offset, 0xffffffffffffffff) { revert(0, 0) } + if gt(offset, 0xffffffffffffffff) { } mstore(add(value, ), (add(headStart, offset), end)) )" : R"( @@ -1313,6 +1336,8 @@ string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fr mstore(add(value, ), (add(headStart, offset), end)) )" ); + // 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))); @@ -1380,7 +1405,7 @@ string ABIFunctions::calldataAccessFunction(Type const& _type) 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)))) { revert(0, 0) } + if iszero(slt(rel_offset_of_tail, sub(sub(calldatasize(), base_ref), sub(, 1)))) { } value := add(rel_offset_of_tail, base_ref) } @@ -1392,9 +1417,15 @@ string ABIFunctions::calldataAccessFunction(Type const& _type) w("handleLength", Whiskers(R"( length := calldataload(value) value := add(value, 0x20) - if gt(length, 0xffffffffffffffff) { revert(0, 0) } - if sgt(base_ref, sub(calldatasize(), mul(length, ))) { revert(0, 0) } - )")("calldataStride", toCompactHexWithPrefix(arrayType->calldataStride())).render()); + 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 @@ -1404,6 +1435,7 @@ string ABIFunctions::calldataAccessFunction(Type const& _type) } w("neededLength", toCompactHexWithPrefix(tailSize)); w("functionName", functionName); + w("revertStringOffset", revertReasonIfDebug("Invalid calldata access offset")); return w.render(); } else if (_type.isValueType()) @@ -1493,3 +1525,8 @@ size_t ABIFunctions::numVariablesForType(Type const& _type, EncodingOptions cons 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 index f8f09a3f3..a0b8333df 100644 --- a/libsolidity/codegen/ABIFunctions.h +++ b/libsolidity/codegen/ABIFunctions.h @@ -25,6 +25,8 @@ #include #include +#include + #include #include @@ -55,11 +57,13 @@ class ABIFunctions public: explicit ABIFunctions( langutil::EVMVersion _evmVersion, + RevertStrings _revertStrings, std::shared_ptr _functionCollector = std::make_shared() ): m_evmVersion(_evmVersion), + m_revertStrings(_revertStrings), m_functionCollector(std::move(_functionCollector)), - m_utils(_evmVersion, m_functionCollector) + m_utils(_evmVersion, m_revertStrings, m_functionCollector) {} /// @returns name of an assembly function to ABI-encode values of @a _givenTypes @@ -249,7 +253,12 @@ private: /// 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; std::shared_ptr m_functionCollector; std::set m_externallyUsedFunctions; YulUtilFunctions m_utils; diff --git a/libsolidity/codegen/Compiler.cpp b/libsolidity/codegen/Compiler.cpp index a6452adb2..78198fff0 100644 --- a/libsolidity/codegen/Compiler.cpp +++ b/libsolidity/codegen/Compiler.cpp @@ -35,7 +35,7 @@ void Compiler::compileContract( bytes const& _metadata ) { - ContractCompiler runtimeCompiler(nullptr, m_runtimeContext, m_optimiserSettings, m_revertStrings); + ContractCompiler runtimeCompiler(nullptr, m_runtimeContext, m_optimiserSettings); runtimeCompiler.compileContract(_contract, _otherCompilers); m_runtimeContext.appendAuxiliaryData(_metadata); @@ -45,7 +45,7 @@ void Compiler::compileContract( // The creation code will be executed at most once, so we modify the optimizer // settings accordingly. creationSettings.expectedExecutionsPerDeployment = 1; - ContractCompiler creationCompiler(&runtimeCompiler, m_context, creationSettings, m_revertStrings); + ContractCompiler creationCompiler(&runtimeCompiler, m_context, creationSettings); m_runtimeSub = creationCompiler.compileConstructor(_contract, _otherCompilers); m_context.optimise(m_optimiserSettings); diff --git a/libsolidity/codegen/Compiler.h b/libsolidity/codegen/Compiler.h index e4a8adc6a..8bd21e586 100644 --- a/libsolidity/codegen/Compiler.h +++ b/libsolidity/codegen/Compiler.h @@ -37,9 +37,8 @@ class Compiler public: Compiler(langutil::EVMVersion _evmVersion, RevertStrings _revertStrings, OptimiserSettings _optimiserSettings): m_optimiserSettings(std::move(_optimiserSettings)), - m_revertStrings(_revertStrings), - m_runtimeContext(_evmVersion), - m_context(_evmVersion, &m_runtimeContext) + m_runtimeContext(_evmVersion, _revertStrings), + m_context(_evmVersion, _revertStrings, &m_runtimeContext) { } /// Compiles a contract. @@ -80,7 +79,6 @@ public: private: OptimiserSettings const m_optimiserSettings; - RevertStrings const m_revertStrings; CompilerContext m_runtimeContext; size_t m_runtimeSub = size_t(-1); ///< Identifier of the runtime sub-assembly, if present. CompilerContext m_context; diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index 645e6e61e..f1f386300 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -37,6 +37,8 @@ #include #include +#include + #include #include #include @@ -55,6 +57,7 @@ using namespace std; using namespace solidity; +using namespace solidity::util; using namespace solidity::evmasm; using namespace solidity::frontend; using namespace solidity::langutil; @@ -296,12 +299,13 @@ CompilerContext& CompilerContext::appendConditionalInvalid() return *this; } -CompilerContext& CompilerContext::appendRevert() +CompilerContext& CompilerContext::appendRevert(string const& _message) { - return *this << u256(0) << u256(0) << Instruction::REVERT; + appendInlineAssembly("{ " + revertReasonIfDebug(_message) + " }"); + return *this; } -CompilerContext& CompilerContext::appendConditionalRevert(bool _forwardReturnData) +CompilerContext& CompilerContext::appendConditionalRevert(bool _forwardReturnData, string const& _message) { if (_forwardReturnData && m_evmVersion.supportsReturndata()) appendInlineAssembly(R"({ @@ -311,9 +315,7 @@ CompilerContext& CompilerContext::appendConditionalRevert(bool _forwardReturnDat } })", {"condition"}); else - appendInlineAssembly(R"({ - if condition { revert(0, 0) } - })", {"condition"}); + appendInlineAssembly("{ if condition { " + revertReasonIfDebug(_message) + " } }", {"condition"}); *this << Instruction::POP; return *this; } @@ -488,6 +490,11 @@ vector::const_iterator CompilerContext::superContract return ++it; } +string CompilerContext::revertReasonIfDebug(string const& _message) +{ + return YulUtilFunctions::revertReasonIfDebug(m_revertStrings, _message); +} + void CompilerContext::updateSourceLocation() { m_asm->setSourceLocation(m_visitedNodes.empty() ? SourceLocation() : m_visitedNodes.top()->location()); diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index 45f16f1f1..28aefa036 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -27,6 +27,7 @@ #include #include +#include #include #include @@ -51,11 +52,16 @@ class Compiler; class CompilerContext { public: - explicit CompilerContext(langutil::EVMVersion _evmVersion, CompilerContext* _runtimeContext = nullptr): + explicit CompilerContext( + langutil::EVMVersion _evmVersion, + RevertStrings _revertStrings, + CompilerContext* _runtimeContext = nullptr + ): m_asm(std::make_shared()), m_evmVersion(_evmVersion), + m_revertStrings(_revertStrings), m_runtimeContext(_runtimeContext), - m_abiFunctions(m_evmVersion) + m_abiFunctions(m_evmVersion, m_revertStrings) { if (m_runtimeContext) m_runtimeSub = size_t(m_asm->newSub(m_runtimeContext->m_asm).data()); @@ -160,12 +166,14 @@ public: /// Appends a conditional INVALID instruction CompilerContext& appendConditionalInvalid(); /// Appends a REVERT(0, 0) call - CompilerContext& appendRevert(); + /// @param _message is an optional revert message used in debug mode + CompilerContext& appendRevert(std::string const& _message = ""); /// Appends a conditional REVERT-call, either forwarding the RETURNDATA or providing the /// empty string. Consumes the condition. /// If the current EVM version does not support RETURNDATA, uses REVERT but does not forward /// the data. - CompilerContext& appendConditionalRevert(bool _forwardReturnData = false); + /// @param _message is an optional revert message used in debug mode + CompilerContext& appendConditionalRevert(bool _forwardReturnData = false, std::string const& _message = ""); /// Appends a JUMP to a specific tag CompilerContext& appendJumpTo( evmasm::AssemblyItem const& _tag, @@ -219,6 +227,11 @@ public: OptimiserSettings const& _optimiserSettings = OptimiserSettings::none() ); + /// If m_revertStrings is debug, @returns inline assembly code that + /// stores @param _message in memory position 0 and reverts. + /// Otherwise returns "revert(0, 0)". + std::string revertReasonIfDebug(std::string const& _message = ""); + /// Appends arbitrary data to the end of the bytecode. void appendAuxiliaryData(bytes const& _data) { m_asm->appendAuxiliaryDataToEnd(_data); } @@ -263,6 +276,8 @@ public: void setModifierDepth(size_t _modifierDepth) { m_asm->m_currentModifierDepth = _modifierDepth; } + RevertStrings revertStrings() const { return m_revertStrings; } + private: /// Searches the inheritance hierarchy towards the base starting from @a _searchStart and returns /// the first function definition that is overwritten by _function. @@ -312,6 +327,7 @@ private: evmasm::AssemblyPointer m_asm; /// Version of the EVM to compile against. langutil::EVMVersion m_evmVersion; + RevertStrings const m_revertStrings; /// Activated experimental features. std::set m_experimentalFeatures; /// Other already compiled contracts to be used in contract creation calls. diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index c15d2537e..4c75081ff 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -130,9 +130,12 @@ void CompilerUtils::accessCalldataTail(Type const& _type) // returns the absolute offset of the tail in "base_ref" m_context.appendInlineAssembly(Whiskers(R"({ let rel_offset_of_tail := calldataload(ptr_to_tail) - if iszero(slt(rel_offset_of_tail, sub(sub(calldatasize(), base_ref), sub(, 1)))) { revert(0, 0) } + if iszero(slt(rel_offset_of_tail, sub(sub(calldatasize(), base_ref), sub(, 1)))) { } base_ref := add(base_ref, rel_offset_of_tail) - })")("neededLength", toCompactHexWithPrefix(tailSize)).render(), {"base_ref", "ptr_to_tail"}); + })") + ("neededLength", toCompactHexWithPrefix(tailSize)) + ("revertString", m_context.revertReasonIfDebug("Invalid calldata tail offset")) + .render(), {"base_ref", "ptr_to_tail"}); // stack layout: if (!_type.isDynamicallySized()) @@ -158,9 +161,12 @@ void CompilerUtils::accessCalldataTail(Type const& _type) Whiskers(R"({ length := calldataload(base_ref) base_ref := add(base_ref, 0x20) - if gt(length, 0xffffffffffffffff) { revert(0, 0) } + if gt(length, 0xffffffffffffffff) { } if sgt(base_ref, sub(calldatasize(), mul(length, ))) { revert(0, 0) } - })")("calldataStride", toCompactHexWithPrefix(calldataStride)).render(), + })") + ("calldataStride", toCompactHexWithPrefix(calldataStride)) + ("revertString", m_context.revertReasonIfDebug("Invalid calldata tail length")) + .render(), {"base_ref", "length"} ); // stack layout: @@ -277,7 +283,13 @@ void CompilerUtils::abiDecode(TypePointers const& _typeParameters, bool _fromMem size_t encodedSize = 0; for (auto const& t: _typeParameters) encodedSize += t->decodingType()->calldataHeadSize(); - m_context.appendInlineAssembly("{ if lt(len, " + to_string(encodedSize) + ") { revert(0, 0) } }", {"len"}); + + Whiskers templ(R"({ + if lt(len, ) { } + })"); + templ("encodedSize", to_string(encodedSize)); + templ("revertString", m_context.revertReasonIfDebug("Calldata too short")); + m_context.appendInlineAssembly(templ.render(), {"len"}); m_context << Instruction::DUP2 << Instruction::ADD; m_context << Instruction::SWAP1; @@ -319,19 +331,23 @@ void CompilerUtils::abiDecode(TypePointers const& _typeParameters, bool _fromMem // Check that the data pointer is valid and that length times // item size is still inside the range. Whiskers templ(R"({ - if gt(ptr, 0x100000000) { revert(0, 0) } + if gt(ptr, 0x100000000) { } ptr := add(ptr, base_offset) let array_data_start := add(ptr, 0x20) - if gt(array_data_start, input_end) { revert(0, 0) } + if gt(array_data_start, input_end) { } let array_length := mload(ptr) if or( gt(array_length, 0x100000000), gt(add(array_data_start, mul(array_length, )), input_end) - ) { revert(0, 0) } + ) { } mstore(dst, array_length) dst := add(dst, 0x20) })"); templ("item_size", to_string(arrayType.calldataStride())); + // TODO add test + templ("revertStringPointer", m_context.revertReasonIfDebug("ABI memory decoding: invalid data pointer")); + templ("revertStringStart", m_context.revertReasonIfDebug("ABI memory decoding: invalid data start")); + templ("revertStringLength", m_context.revertReasonIfDebug("ABI memory decoding: invalid data length")); m_context.appendInlineAssembly(templ.render(), {"input_end", "base_offset", "offset", "ptr", "dst"}); // stack: v1 v2 ... v(k-1) dstmem input_end base_offset current_offset data_ptr dstdata m_context << Instruction::SWAP1; @@ -359,24 +375,33 @@ void CompilerUtils::abiDecode(TypePointers const& _typeParameters, bool _fromMem loadFromMemoryDynamic(*TypeProvider::uint256(), !_fromMemory); m_context << Instruction::SWAP1; // stack: input_end base_offset next_pointer data_offset - m_context.appendInlineAssembly("{ if gt(data_offset, 0x100000000) { revert(0, 0) } }", {"data_offset"}); + m_context.appendInlineAssembly(Whiskers(R"({ + if gt(data_offset, 0x100000000) { } + })") + // TODO add test + ("revertString", m_context.revertReasonIfDebug("ABI calldata decoding: invalid data offset")) + .render(), {"data_offset"}); m_context << Instruction::DUP3 << Instruction::ADD; // stack: input_end base_offset next_pointer array_head_ptr - m_context.appendInlineAssembly( - "{ if gt(add(array_head_ptr, 0x20), input_end) { revert(0, 0) } }", - {"input_end", "base_offset", "next_ptr", "array_head_ptr"} - ); + m_context.appendInlineAssembly(Whiskers(R"({ + if gt(add(array_head_ptr, 0x20), input_end) { } + })") + ("revertString", m_context.revertReasonIfDebug("ABI calldata decoding: invalid head pointer")) + .render(), {"input_end", "base_offset", "next_ptr", "array_head_ptr"}); + // retrieve length loadFromMemoryDynamic(*TypeProvider::uint256(), !_fromMemory, true); // stack: input_end base_offset next_pointer array_length data_pointer m_context << Instruction::SWAP2; // stack: input_end base_offset data_pointer array_length next_pointer - m_context.appendInlineAssembly(R"({ + m_context.appendInlineAssembly(Whiskers(R"({ if or( gt(array_length, 0x100000000), gt(add(data_ptr, mul(array_length, )" + to_string(arrayType.calldataStride()) + R"()), input_end) - ) { revert(0, 0) } - })", {"input_end", "base_offset", "data_ptr", "array_length", "next_ptr"}); + ) { } + })") + ("revertString", m_context.revertReasonIfDebug("ABI calldata decoding: invalid data pointer")) + .render(), {"input_end", "base_offset", "data_ptr", "array_length", "next_ptr"}); } else { @@ -792,8 +817,7 @@ void CompilerUtils::convertType( solAssert(enumType.numberOfMembers() > 0, "empty enum should have caused a parser error."); m_context << u256(enumType.numberOfMembers() - 1) << Instruction::DUP2 << Instruction::GT; if (_asPartOfArgumentDecoding) - // TODO: error message? - m_context.appendConditionalRevert(); + m_context.appendConditionalRevert(false, "Enum out of range"); else m_context.appendConditionalInvalid(); enumOverflowCheckPending = false; diff --git a/libsolidity/codegen/CompilerUtils.h b/libsolidity/codegen/CompilerUtils.h index 5cc0ac9c8..3d5d85825 100644 --- a/libsolidity/codegen/CompilerUtils.h +++ b/libsolidity/codegen/CompilerUtils.h @@ -24,6 +24,7 @@ #include #include +#include #include #include @@ -34,7 +35,8 @@ class Type; // forward class CompilerUtils { public: - explicit CompilerUtils(CompilerContext& _context): m_context(_context) {} + explicit CompilerUtils(CompilerContext& _context): m_context(_context) + {} /// Stores the initial value of the free-memory-pointer at its position; void initialiseFreeMemoryPointer(); diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index ecf62d29a..bbf6472de 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -128,8 +128,7 @@ void ContractCompiler::appendCallValueCheck() { // Throw if function is not payable but call contained ether. m_context << Instruction::CALLVALUE; - // TODO: error message? - m_context.appendConditionalRevert(); + m_context.appendConditionalRevert(false, "Ether sent to non-payable function"); } void ContractCompiler::appendInitAndConstructorCode(ContractDefinition const& _contract) @@ -409,7 +408,7 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac m_context << notFoundOrReceiveEther; if (!fallback && !etherReceiver) - m_context.appendRevert(); + m_context.appendRevert("Contract does not have fallback nor receive functions"); else { if (etherReceiver) @@ -440,8 +439,7 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac m_context << Instruction::STOP; } else - // TODO: error message here? - m_context.appendRevert(); + m_context.appendRevert("Unknown signature and no fallback defined"); } @@ -457,7 +455,7 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac // If the function is not a view function and is called without DELEGATECALL, // we revert. m_context << dupInstruction(2); - m_context.appendConditionalRevert(); + 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 @@ -517,7 +515,7 @@ void ContractCompiler::initializeStateVariables(ContractDefinition const& _contr solAssert(!_contract.isLibrary(), "Tried to initialize state variables of library."); for (VariableDeclaration const* variable: _contract.stateVariables()) if (variable->value() && !variable->isConstant()) - ExpressionCompiler(m_context, m_revertStrings, m_optimiserSettings.runOrderLiterals).appendStateVariableInitialization(*variable); + ExpressionCompiler(m_context, m_optimiserSettings.runOrderLiterals).appendStateVariableInitialization(*variable); } bool ContractCompiler::visit(VariableDeclaration const& _variableDeclaration) @@ -530,10 +528,10 @@ bool ContractCompiler::visit(VariableDeclaration const& _variableDeclaration) m_continueTags.clear(); if (_variableDeclaration.isConstant()) - ExpressionCompiler(m_context, m_revertStrings, m_optimiserSettings.runOrderLiterals) + ExpressionCompiler(m_context, m_optimiserSettings.runOrderLiterals) .appendConstStateVariableAccessor(_variableDeclaration); else - ExpressionCompiler(m_context, m_revertStrings, m_optimiserSettings.runOrderLiterals) + ExpressionCompiler(m_context, m_optimiserSettings.runOrderLiterals) .appendStateVariableAccessor(_variableDeclaration); return false; @@ -954,7 +952,8 @@ void ContractCompiler::handleCatch(vector> const& _ca revert(0, returndatasize()) })"); else - m_context.appendRevert(); + // Since both returndata and revert are >=byzantium, this should be unreachable. + solAssert(false, ""); } m_context << endTag; } @@ -1316,7 +1315,7 @@ void ContractCompiler::appendStackVariableInitialisation(VariableDeclaration con void ContractCompiler::compileExpression(Expression const& _expression, TypePointer const& _targetType) { - ExpressionCompiler expressionCompiler(m_context, m_revertStrings, m_optimiserSettings.runOrderLiterals); + ExpressionCompiler expressionCompiler(m_context, m_optimiserSettings.runOrderLiterals); expressionCompiler.compile(_expression); if (_targetType) CompilerUtils(m_context).convertType(*_expression.annotation().type, *_targetType); diff --git a/libsolidity/codegen/ContractCompiler.h b/libsolidity/codegen/ContractCompiler.h index 02fcbe4ca..0a2ecf7e8 100644 --- a/libsolidity/codegen/ContractCompiler.h +++ b/libsolidity/codegen/ContractCompiler.h @@ -43,11 +43,9 @@ public: explicit ContractCompiler( ContractCompiler* _runtimeCompiler, CompilerContext& _context, - OptimiserSettings _optimiserSettings, - RevertStrings _revertStrings + OptimiserSettings _optimiserSettings ): m_optimiserSettings(std::move(_optimiserSettings)), - m_revertStrings(_revertStrings), m_runtimeCompiler(_runtimeCompiler), m_context(_context) { @@ -140,7 +138,6 @@ private: void storeStackHeight(ASTNode const* _node); OptimiserSettings const m_optimiserSettings; - RevertStrings const m_revertStrings; /// Pointer to the runtime compiler in case this is a creation compiler. ContractCompiler* m_runtimeCompiler = nullptr; CompilerContext& m_context; diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index a2812ce51..c8ded9078 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -677,7 +677,6 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) m_context << errorCase; } else - // TODO: Can we bubble up here? There might be different reasons for failure, I think. m_context.appendConditionalRevert(true); break; } @@ -734,8 +733,8 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) if (function.kind() == FunctionType::Kind::Transfer) { // Check if zero (out of stack or not enough balance). - // TODO: bubble up here, but might also be different error. m_context << Instruction::ISZERO; + // Revert message bubbles up. m_context.appendConditionalRevert(true); } break; @@ -752,7 +751,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) // function-sel(Error(string)) + encoding solAssert(arguments.size() == 1, ""); solAssert(function.parameterTypes().size() == 1, ""); - if (m_revertStrings == RevertStrings::Strip) + if (m_context.revertStrings() == RevertStrings::Strip) { if (!arguments.front()->annotation().isPure) { @@ -1032,7 +1031,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) { acceptAndConvert(*arguments.front(), *function.parameterTypes().front(), false); - bool haveReasonString = arguments.size() > 1 && m_revertStrings != RevertStrings::Strip; + bool haveReasonString = arguments.size() > 1 && m_context.revertStrings() != RevertStrings::Strip; if (arguments.size() > 1) { @@ -1041,7 +1040,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) // function call. solAssert(arguments.size() == 2, ""); solAssert(function.kind() == FunctionType::Kind::Require, ""); - if (m_revertStrings == RevertStrings::Strip) + if (m_context.revertStrings() == RevertStrings::Strip) { if (!arguments.at(1)->annotation().isPure) { @@ -1821,12 +1820,16 @@ bool ExpressionCompiler::visit(IndexRangeAccess const& _indexAccess) m_context.appendInlineAssembly( Whiskers(R"({ - if gt(sliceStart, sliceEnd) { revert(0, 0) } - if gt(sliceEnd, length) { revert(0, 0) } + if gt(sliceStart, sliceEnd) { } + if gt(sliceEnd, length) { } offset := add(offset, mul(sliceStart, )) length := sub(sliceEnd, sliceStart) - })")("stride", toString(arrayType->calldataStride())).render(), + })") + ("stride", toString(arrayType->calldataStride())) + ("revertStringStartEnd", m_context.revertReasonIfDebug("Slice starts after end")) + ("revertStringEndLength", m_context.revertReasonIfDebug("Slice is greater than length")) + .render(), {"offset", "length", "sliceStart", "sliceEnd"} ); @@ -2299,8 +2302,7 @@ void ExpressionCompiler::appendExternalFunctionCall( if (funKind == FunctionType::Kind::External || funKind == FunctionType::Kind::DelegateCall) { m_context << Instruction::DUP1 << Instruction::EXTCODESIZE << Instruction::ISZERO; - // TODO: error message? - m_context.appendConditionalRevert(); + m_context.appendConditionalRevert(false, "Target contract does not contain code"); existenceChecked = true; } diff --git a/libsolidity/codegen/ExpressionCompiler.h b/libsolidity/codegen/ExpressionCompiler.h index a64efd657..33fb2696f 100644 --- a/libsolidity/codegen/ExpressionCompiler.h +++ b/libsolidity/codegen/ExpressionCompiler.h @@ -58,10 +58,8 @@ class ExpressionCompiler: private ASTConstVisitor public: ExpressionCompiler( CompilerContext& _compilerContext, - RevertStrings _revertStrings, bool _optimiseOrderLiterals ): - m_revertStrings(_revertStrings), m_optimiseOrderLiterals(_optimiseOrderLiterals), m_context(_compilerContext) {} @@ -139,7 +137,6 @@ private: /// @returns the CompilerUtils object containing the current context. CompilerUtils utils(); - RevertStrings m_revertStrings; bool m_optimiseOrderLiterals; CompilerContext& m_context; std::unique_ptr m_currentLValue; diff --git a/libsolidity/codegen/YulUtilFunctions.cpp b/libsolidity/codegen/YulUtilFunctions.cpp index b06d07182..4cc2c8714 100644 --- a/libsolidity/codegen/YulUtilFunctions.cpp +++ b/libsolidity/codegen/YulUtilFunctions.cpp @@ -134,7 +134,7 @@ string YulUtilFunctions::requireOrAssertFunction(bool _assert, Type const* _mess FixedHash(keccak256("Error(string)")) )) << (256 - hashHeaderSize * byteSize); - string const encodeFunc = ABIFunctions(m_evmVersion, m_functionCollector) + string const encodeFunc = ABIFunctions(m_evmVersion, m_revertStrings, m_functionCollector) .tupleEncoder( {_messageType}, {TypeProvider::stringMemory()} @@ -1627,7 +1627,7 @@ string YulUtilFunctions::packedHashFunction( templ("variables", suffixedVariableNameList("var_", 1, 1 + sizeOnStack)); templ("comma", sizeOnStack > 0 ? "," : ""); templ("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer)); - templ("packedEncode", ABIFunctions(m_evmVersion, m_functionCollector).tupleEncoderPacked(_givenTypes, _targetTypes)); + templ("packedEncode", ABIFunctions(m_evmVersion, m_revertStrings, m_functionCollector).tupleEncoderPacked(_givenTypes, _targetTypes)); return templ.render(); }); } @@ -1905,3 +1905,41 @@ string YulUtilFunctions::readFromMemoryOrCalldata(Type const& _type, bool _fromC .render(); }); } + +string YulUtilFunctions::revertReasonIfDebug(RevertStrings revertStrings, string const& _message) +{ + if (revertStrings >= RevertStrings::Debug && !_message.empty()) + { + Whiskers templ(R"({ + mstore(0, ) + mstore(4, 0x20) + mstore(add(4, 0x20), ) + let reasonPos := add(4, 0x40) + <#word> + mstore(add(reasonPos, ), ) + + revert(0, add(reasonPos, )) + })"); + templ("sig", (u256(util::FixedHash<4>::Arith(util::FixedHash<4>(util::keccak256("Error(string)")))) << (256 - 32)).str()); + templ("length", to_string(_message.length())); + + size_t words = (_message.length() + 31) / 32; + vector> wordParams(words); + for (size_t i = 0; i < words; ++i) + { + wordParams[i]["offset"] = to_string(i * 32); + wordParams[i]["wordValue"] = formatAsStringOrNumber(_message.substr(32 * i, 32)); + } + templ("word", wordParams); + templ("end", to_string(words * 32)); + + return templ.render(); + } + else + return "revert(0, 0)"; +} + +string YulUtilFunctions::revertReasonIfDebug(string const& _message) +{ + return revertReasonIfDebug(m_revertStrings, _message); +} diff --git a/libsolidity/codegen/YulUtilFunctions.h b/libsolidity/codegen/YulUtilFunctions.h index df6292b42..859290ffa 100644 --- a/libsolidity/codegen/YulUtilFunctions.h +++ b/libsolidity/codegen/YulUtilFunctions.h @@ -24,6 +24,8 @@ #include +#include + #include #include #include @@ -44,9 +46,11 @@ class YulUtilFunctions public: explicit YulUtilFunctions( langutil::EVMVersion _evmVersion, + RevertStrings _revertStrings, std::shared_ptr _functionCollector ): m_evmVersion(_evmVersion), + m_revertStrings(_revertStrings), m_functionCollector(std::move(_functionCollector)) {} @@ -281,6 +285,13 @@ public: /// zero /// signature: (slot, offset) -> std::string storageSetToZeroFunction(Type const& _type); + + /// If revertStrings is debug, @returns inline assembly code that + /// stores @param _message in memory position 0 and reverts. + /// Otherwise returns "revert(0, 0)". + static std::string revertReasonIfDebug(RevertStrings revertStrings, std::string const& _message = ""); + + std::string revertReasonIfDebug(std::string const& _message = ""); private: /// Special case of conversionFunction - handles everything that does not /// use exactly one variable to hold the value. @@ -289,6 +300,7 @@ private: std::string readFromMemoryOrCalldata(Type const& _type, bool _fromCalldata); langutil::EVMVersion m_evmVersion; + RevertStrings m_revertStrings; std::shared_ptr m_functionCollector; }; diff --git a/libsolidity/codegen/ir/IRGenerationContext.cpp b/libsolidity/codegen/ir/IRGenerationContext.cpp index 9c8e3b974..bd54e1fbd 100644 --- a/libsolidity/codegen/ir/IRGenerationContext.cpp +++ b/libsolidity/codegen/ir/IRGenerationContext.cpp @@ -133,7 +133,7 @@ string IRGenerationContext::internalDispatch(size_t _in, size_t _out) )"); templ("functionName", funName); templ("comma", _in > 0 ? "," : ""); - YulUtilFunctions utils(m_evmVersion, m_functions); + YulUtilFunctions utils(m_evmVersion, m_revertStrings, m_functions); templ("in", suffixedVariableNameList("in_", 0, _in)); templ("arrow", _out > 0 ? "->" : ""); templ("out", suffixedVariableNameList("out_", 0, _out)); @@ -161,5 +161,10 @@ string IRGenerationContext::internalDispatch(size_t _in, size_t _out) YulUtilFunctions IRGenerationContext::utils() { - return YulUtilFunctions(m_evmVersion, m_functions); + return YulUtilFunctions(m_evmVersion, m_revertStrings, m_functions); +} + +std::string IRGenerationContext::revertReasonIfDebug(std::string const& _message) +{ + return YulUtilFunctions::revertReasonIfDebug(m_revertStrings, _message); } diff --git a/libsolidity/codegen/ir/IRGenerationContext.h b/libsolidity/codegen/ir/IRGenerationContext.h index 9c1844f08..698a6d38a 100644 --- a/libsolidity/codegen/ir/IRGenerationContext.h +++ b/libsolidity/codegen/ir/IRGenerationContext.h @@ -21,6 +21,7 @@ #pragma once #include +#include #include @@ -47,8 +48,13 @@ class YulUtilFunctions; class IRGenerationContext { public: - IRGenerationContext(langutil::EVMVersion _evmVersion, OptimiserSettings _optimiserSettings): + IRGenerationContext( + langutil::EVMVersion _evmVersion, + RevertStrings _revertStrings, + OptimiserSettings _optimiserSettings + ): m_evmVersion(_evmVersion), + m_revertStrings(_revertStrings), m_optimiserSettings(std::move(_optimiserSettings)), m_functions(std::make_shared()) {} @@ -92,8 +98,15 @@ public: langutil::EVMVersion evmVersion() const { return m_evmVersion; }; + /// @returns code that stores @param _message for revert reason + /// if m_revertStrings is debug. + std::string revertReasonIfDebug(std::string const& _message = ""); + + RevertStrings revertStrings() const { return m_revertStrings; } + private: langutil::EVMVersion m_evmVersion; + RevertStrings m_revertStrings; OptimiserSettings m_optimiserSettings; std::vector m_inheritanceHierarchy; std::map m_localVariables; diff --git a/libsolidity/codegen/ir/IRGenerator.cpp b/libsolidity/codegen/ir/IRGenerator.cpp index 0798ebc37..69c47df00 100644 --- a/libsolidity/codegen/ir/IRGenerator.cpp +++ b/libsolidity/codegen/ir/IRGenerator.cpp @@ -333,7 +333,7 @@ string IRGenerator::dispatchRoutine(ContractDefinition const& _contract) templ["assignToParams"] = paramVars == 0 ? "" : "let " + suffixedVariableNameList("param_", 0, paramVars) + " := "; templ["assignToRetParams"] = retVars == 0 ? "" : "let " + suffixedVariableNameList("ret_", 0, retVars) + " := "; - ABIFunctions abiFunctions(m_evmVersion, m_context.functionCollector()); + ABIFunctions abiFunctions(m_evmVersion, m_context.revertStrings(), m_context.functionCollector()); templ["abiDecode"] = abiFunctions.tupleDecoder(type->parameterTypes()); templ["params"] = suffixedVariableNameList("param_", 0, paramVars); templ["retParams"] = suffixedVariableNameList("ret_", retVars, 0); @@ -386,8 +386,8 @@ void IRGenerator::resetContext(ContractDefinition const& _contract) m_context.functionCollector()->requestedFunctions().empty(), "Reset context while it still had functions." ); - m_context = IRGenerationContext(m_evmVersion, m_optimiserSettings); - m_utils = YulUtilFunctions(m_evmVersion, m_context.functionCollector()); + m_context = IRGenerationContext(m_evmVersion, m_context.revertStrings(), m_optimiserSettings); + m_utils = YulUtilFunctions(m_evmVersion, m_context.revertStrings(), m_context.functionCollector()); m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts); for (auto const& var: ContractType(_contract).stateVariables()) diff --git a/libsolidity/codegen/ir/IRGenerator.h b/libsolidity/codegen/ir/IRGenerator.h index 2481f08dd..d1ec580f6 100644 --- a/libsolidity/codegen/ir/IRGenerator.h +++ b/libsolidity/codegen/ir/IRGenerator.h @@ -37,11 +37,15 @@ class SourceUnit; class IRGenerator { public: - IRGenerator(langutil::EVMVersion _evmVersion, OptimiserSettings _optimiserSettings): + IRGenerator( + langutil::EVMVersion _evmVersion, + RevertStrings _revertStrings, + OptimiserSettings _optimiserSettings + ): m_evmVersion(_evmVersion), m_optimiserSettings(_optimiserSettings), - m_context(_evmVersion, std::move(_optimiserSettings)), - m_utils(_evmVersion, m_context.functionCollector()) + m_context(_evmVersion, _revertStrings, std::move(_optimiserSettings)), + m_utils(_evmVersion, m_context.revertStrings(), m_context.functionCollector()) {} /// Generates and returns the IR code, in unoptimized and optimized form diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index 16c11e3e7..2d38fe026 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -534,7 +534,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) { auto const& event = dynamic_cast(functionType->declaration()); TypePointers paramTypes = functionType->parameterTypes(); - ABIFunctions abi(m_context.evmVersion(), m_context.functionCollector()); + ABIFunctions abi(m_context.evmVersion(), m_context.revertStrings(), m_context.functionCollector()); vector indexedArgs; string nonIndexedArgs; @@ -1151,7 +1151,7 @@ void IRGeneratorForStatements::appendExternalFunctionCall( m_code << "mstore(add(" << fetchFreeMem() << ", " << to_string(retSize) << "), 0)\n"; } - ABIFunctions abi(m_context.evmVersion(), m_context.functionCollector()); + ABIFunctions abi(m_context.evmVersion(), m_context.revertStrings(), m_context.functionCollector()); solUnimplementedAssert(!funType.isBareCall(), ""); Whiskers templ(R"( diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index dfa7629da..6940730cf 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -165,7 +165,7 @@ void CompilerStack::setRevertStringBehaviour(RevertStrings _revertStrings) { if (m_stackState >= ParsingPerformed) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must set revert string settings before parsing.")); - solUnimplementedAssert(_revertStrings == RevertStrings::Default || _revertStrings == RevertStrings::Strip, ""); + solUnimplementedAssert(_revertStrings != RevertStrings::VerboseDebug, ""); m_revertStrings = _revertStrings; } @@ -1112,7 +1112,7 @@ void CompilerStack::generateIR(ContractDefinition const& _contract) for (auto const* dependency: _contract.annotation().contractDependencies) generateIR(*dependency); - IRGenerator generator(m_evmVersion, m_optimiserSettings); + IRGenerator generator(m_evmVersion, m_revertStrings, m_optimiserSettings); tie(compiledContract.yulIR, compiledContract.yulIROptimized) = generator.run(_contract); } diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index 3a86914d1..1b09b2f2c 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -661,10 +661,10 @@ boost::variant StandardCompile std::optional revertStrings = revertStringsFromString(settings["debug"]["revertStrings"].asString()); if (!revertStrings) return formatFatalError("JSONError", "Invalid value for settings.debug.revertStrings."); - if (*revertStrings != RevertStrings::Default && *revertStrings != RevertStrings::Strip) + if (*revertStrings == RevertStrings::VerboseDebug) return formatFatalError( "UnimplementedFeatureError", - "Only \"default\" and \"strip\" are implemented for settings.debug.revertStrings for now." + "Only \"default\", \"strip\" and \"debug\" are implemented for settings.debug.revertStrings for now." ); ret.revertStrings = *revertStrings; } diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 1598bbb68..57cdaf943 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -875,9 +875,9 @@ Allowed options)", serr() << "Invalid option for --" << g_strRevertStrings << ": " << revertStringsString << endl; return false; } - if (*revertStrings != RevertStrings::Default && *revertStrings != RevertStrings::Strip) + if (*revertStrings == RevertStrings::VerboseDebug) { - serr() << "Only \"default\" and \"strip\" are implemented for --" << g_strRevertStrings << " for now." << endl; + serr() << "Only \"default\", \"strip\" and \"debug\" are implemented for --" << g_strRevertStrings << " for now." << endl; return false; } m_revertStrings = *revertStrings; diff --git a/test/libsolidity/Assembly.cpp b/test/libsolidity/Assembly.cpp index 3c403f74e..f762849e8 100644 --- a/test/libsolidity/Assembly.cpp +++ b/test/libsolidity/Assembly.cpp @@ -169,26 +169,32 @@ BOOST_AUTO_TEST_CASE(location_test) if (solidity::test::CommonOptions::get().optimize) locations = vector(4, SourceLocation{2, 82, sourceCode}) + - vector(1, SourceLocation{8, 17, codegenCharStream}) + - vector(3, SourceLocation{5, 7, codegenCharStream}) + - vector(1, SourceLocation{30, 31, codegenCharStream}) + + vector(1, SourceLocation{5, 14, codegenCharStream}) + + vector(3, SourceLocation{2, 4, codegenCharStream}) + vector(1, SourceLocation{27, 28, codegenCharStream}) + - vector(1, SourceLocation{20, 32, codegenCharStream}) + - vector(1, SourceLocation{5, 7, codegenCharStream}) + - vector(19, SourceLocation{2, 82, sourceCode}) + + vector(1, SourceLocation{24, 25, codegenCharStream}) + + vector(1, SourceLocation{17, 29, codegenCharStream}) + + vector(1, SourceLocation{2, 4, codegenCharStream}) + + vector(16, SourceLocation{2, 82, sourceCode}) + + vector(1, SourceLocation{12, 13, codegenCharStream}) + + vector(1, SourceLocation{9, 10, codegenCharStream}) + + vector(1, SourceLocation{2, 14, codegenCharStream}) + vector(21, SourceLocation{20, 79, sourceCode}) + vector(1, SourceLocation{72, 74, sourceCode}) + vector(2, SourceLocation{20, 79, sourceCode}); else locations = vector(4, SourceLocation{2, 82, sourceCode}) + - vector(1, SourceLocation{8, 17, codegenCharStream}) + - vector(3, SourceLocation{5, 7, codegenCharStream}) + - vector(1, SourceLocation{30, 31, codegenCharStream}) + + vector(1, SourceLocation{5, 14, codegenCharStream}) + + vector(3, SourceLocation{2, 4, codegenCharStream}) + vector(1, SourceLocation{27, 28, codegenCharStream}) + - vector(1, SourceLocation{20, 32, codegenCharStream}) + - vector(1, SourceLocation{5, 7, codegenCharStream}) + - vector(hasShifts ? 19 : 20, SourceLocation{2, 82, sourceCode}) + + vector(1, SourceLocation{24, 25, codegenCharStream}) + + vector(1, SourceLocation{17, 29, codegenCharStream}) + + vector(1, SourceLocation{2, 4, codegenCharStream}) + + vector(hasShifts ? 16 : 17, SourceLocation{2, 82, sourceCode}) + + vector(1, SourceLocation{12, 13, codegenCharStream}) + + vector(1, SourceLocation{9, 10, codegenCharStream}) + + vector(1, SourceLocation{2, 14, codegenCharStream}) + vector(24, SourceLocation{20, 79, sourceCode}) + vector(1, SourceLocation{49, 58, sourceCode}) + vector(1, SourceLocation{72, 74, sourceCode}) + diff --git a/test/libsolidity/SemanticTest.cpp b/test/libsolidity/SemanticTest.cpp index 027fcf1a0..f083fae76 100644 --- a/test/libsolidity/SemanticTest.cpp +++ b/test/libsolidity/SemanticTest.cpp @@ -70,6 +70,16 @@ SemanticTest::SemanticTest(string const& _filename, langutil::EVMVersion _evmVer } m_settings.erase("ABIEncoderV1Only"); } + + if (m_settings.count("revertStrings")) + { + auto revertStrings = revertStringsFromString(m_settings["revertStrings"]); + if (revertStrings) + m_revertStrings = *revertStrings; + m_validatedSettings["revertStrings"] = revertStringsToString(m_revertStrings); + m_settings.erase("revertStrings"); + } + parseExpectations(file); soltestAssert(!m_tests.empty(), "No tests specified in " + _filename); } diff --git a/test/libsolidity/SolidityExecutionFramework.cpp b/test/libsolidity/SolidityExecutionFramework.cpp index 8f428f13f..dcfb3e6bf 100644 --- a/test/libsolidity/SolidityExecutionFramework.cpp +++ b/test/libsolidity/SolidityExecutionFramework.cpp @@ -51,6 +51,7 @@ bytes SolidityExecutionFramework::compileContract( m_compiler.setEVMVersion(m_evmVersion); m_compiler.setOptimiserSettings(m_optimiserSettings); m_compiler.enableIRGeneration(m_compileViaYul); + m_compiler.setRevertStringBehaviour(m_revertStrings); if (!m_compiler.compile()) { langutil::SourceReferenceFormatter formatter(std::cerr); diff --git a/test/libsolidity/SolidityExecutionFramework.h b/test/libsolidity/SolidityExecutionFramework.h index 28246a26c..dfcf4a46b 100644 --- a/test/libsolidity/SolidityExecutionFramework.h +++ b/test/libsolidity/SolidityExecutionFramework.h @@ -27,6 +27,7 @@ #include #include +#include #include @@ -67,6 +68,8 @@ public: protected: solidity::frontend::CompilerStack m_compiler; bool m_compileViaYul = false; + RevertStrings m_revertStrings = RevertStrings::Default; + }; } // end namespaces diff --git a/test/libsolidity/SolidityExpressionCompiler.cpp b/test/libsolidity/SolidityExpressionCompiler.cpp index 0cafe42a4..cb0066bee 100644 --- a/test/libsolidity/SolidityExpressionCompiler.cpp +++ b/test/libsolidity/SolidityExpressionCompiler.cpp @@ -139,7 +139,10 @@ bytes compileFirstExpression( FirstExpressionExtractor extractor(*contract); BOOST_REQUIRE(extractor.expression() != nullptr); - CompilerContext context(solidity::test::CommonOptions::get().evmVersion()); + CompilerContext context( + solidity::test::CommonOptions::get().evmVersion(), + RevertStrings::Default + ); context.resetVisitedNodes(contract); context.setInheritanceHierarchy(inheritanceHierarchy); unsigned parametersSize = _localVariables.size(); // assume they are all one slot on the stack @@ -152,7 +155,6 @@ bytes compileFirstExpression( ExpressionCompiler( context, - RevertStrings::Default, solidity::test::CommonOptions::get().optimize ).compile(*extractor.expression()); diff --git a/test/libsolidity/StandardCompiler.cpp b/test/libsolidity/StandardCompiler.cpp index 31c37060a..6ade0849f 100644 --- a/test/libsolidity/StandardCompiler.cpp +++ b/test/libsolidity/StandardCompiler.cpp @@ -366,14 +366,15 @@ BOOST_AUTO_TEST_CASE(basic_compilation) BOOST_CHECK(contract["evm"]["assembly"].isString()); BOOST_CHECK(contract["evm"]["assembly"].asString().find( " /* \"fileA\":0:14 contract A { } */\n mstore(0x40, 0x80)\n " - "callvalue\n /* \"--CODEGEN--\":8:17 */\n dup1\n " - "/* \"--CODEGEN--\":5:7 */\n iszero\n tag_1\n jumpi\n " - "/* \"--CODEGEN--\":30:31 */\n 0x00\n /* \"--CODEGEN--\":27:28 */\n " - "dup1\n /* \"--CODEGEN--\":20:32 */\n revert\n /* \"--CODEGEN--\":5:7 */\n" + "callvalue\n /* \"--CODEGEN--\":5:14 */\n dup1\n " + "/* \"--CODEGEN--\":2:4 */\n iszero\n tag_1\n jumpi\n " + "/* \"--CODEGEN--\":27:28 */\n 0x00\n /* \"--CODEGEN--\":24:25 */\n " + "dup1\n /* \"--CODEGEN--\":17:29 */\n revert\n /* \"--CODEGEN--\":2:4 */\n" "tag_1:\n /* \"fileA\":0:14 contract A { } */\n pop\n dataSize(sub_0)\n dup1\n " "dataOffset(sub_0)\n 0x00\n codecopy\n 0x00\n return\nstop\n\nsub_0: assembly {\n " - "/* \"fileA\":0:14 contract A { } */\n mstore(0x40, 0x80)\n 0x00\n " - "dup1\n revert\n\n auxdata: 0xa26469706673582212" + "/* \"fileA\":0:14 contract A { } */\n mstore(0x40, 0x80)\n " + "/* \"--CODEGEN--\":12:13 */\n 0x00\n /* \"--CODEGEN--\":9:10 */\n " + "dup1\n /* \"--CODEGEN--\":2:14 */\n revert\n\n auxdata: 0xa26469706673582212" ) == 0); BOOST_CHECK(contract["evm"]["gasEstimates"].isObject()); BOOST_CHECK_EQUAL(contract["evm"]["gasEstimates"].size(), 1); @@ -397,15 +398,15 @@ BOOST_AUTO_TEST_CASE(basic_compilation) "{\"begin\":0,\"end\":14,\"name\":\"PUSH\",\"value\":\"40\"}," "{\"begin\":0,\"end\":14,\"name\":\"MSTORE\"}," "{\"begin\":0,\"end\":14,\"name\":\"CALLVALUE\"}," - "{\"begin\":8,\"end\":17,\"name\":\"DUP1\"}," - "{\"begin\":5,\"end\":7,\"name\":\"ISZERO\"}," - "{\"begin\":5,\"end\":7,\"name\":\"PUSH [tag]\",\"value\":\"1\"}," - "{\"begin\":5,\"end\":7,\"name\":\"JUMPI\"}," - "{\"begin\":30,\"end\":31,\"name\":\"PUSH\",\"value\":\"0\"}," - "{\"begin\":27,\"end\":28,\"name\":\"DUP1\"}," - "{\"begin\":20,\"end\":32,\"name\":\"REVERT\"}," - "{\"begin\":5,\"end\":7,\"name\":\"tag\",\"value\":\"1\"}," - "{\"begin\":5,\"end\":7,\"name\":\"JUMPDEST\"}," + "{\"begin\":5,\"end\":14,\"name\":\"DUP1\"}," + "{\"begin\":2,\"end\":4,\"name\":\"ISZERO\"}," + "{\"begin\":2,\"end\":4,\"name\":\"PUSH [tag]\",\"value\":\"1\"}," + "{\"begin\":2,\"end\":4,\"name\":\"JUMPI\"}," + "{\"begin\":27,\"end\":28,\"name\":\"PUSH\",\"value\":\"0\"}," + "{\"begin\":24,\"end\":25,\"name\":\"DUP1\"}," + "{\"begin\":17,\"end\":29,\"name\":\"REVERT\"}," + "{\"begin\":2,\"end\":4,\"name\":\"tag\",\"value\":\"1\"}," + "{\"begin\":2,\"end\":4,\"name\":\"JUMPDEST\"}," "{\"begin\":0,\"end\":14,\"name\":\"POP\"}," "{\"begin\":0,\"end\":14,\"name\":\"PUSH #[$]\",\"value\":\"0000000000000000000000000000000000000000000000000000000000000000\"}," "{\"begin\":0,\"end\":14,\"name\":\"DUP1\"}," diff --git a/test/libsolidity/semanticTests/revertStrings/array_slices.sol b/test/libsolidity/semanticTests/revertStrings/array_slices.sol new file mode 100644 index 000000000..dec3e916d --- /dev/null +++ b/test/libsolidity/semanticTests/revertStrings/array_slices.sol @@ -0,0 +1,11 @@ +contract C { + function f(uint256 start, uint256 end, uint256[] calldata arr) external pure { + arr[start:end]; + } +} +// ==== +// EVMVersion: >=byzantium +// revertStrings: debug +// ---- +// f(uint256,uint256,uint256[]): 2, 1, 0x80, 3, 1, 2, 3 -> FAILURE, hex"08c379a0", 0x20, 22, "Slice starts after end" +// f(uint256,uint256,uint256[]): 1, 5, 0x80, 3, 1, 2, 3 -> FAILURE, hex"08c379a0", 0x20, 28, "Slice is greater than length" diff --git a/test/libsolidity/semanticTests/revertStrings/bubble.sol b/test/libsolidity/semanticTests/revertStrings/bubble.sol new file mode 100644 index 000000000..5e9a0730d --- /dev/null +++ b/test/libsolidity/semanticTests/revertStrings/bubble.sol @@ -0,0 +1,15 @@ +contract A { + function g() public { revert("fail"); } +} + +contract C { + A a = new A(); + function f() public { + a.g(); + } +} +// ==== +// EVMVersion: >=byzantium +// revertStrings: debug +// ---- +// f() -> FAILURE, hex"08c379a0", 0x20, 4, "fail" diff --git a/test/libsolidity/semanticTests/revertStrings/calldata_array_dynamic_invalid.sol b/test/libsolidity/semanticTests/revertStrings/calldata_array_dynamic_invalid.sol new file mode 100644 index 000000000..2538a2175 --- /dev/null +++ b/test/libsolidity/semanticTests/revertStrings/calldata_array_dynamic_invalid.sol @@ -0,0 +1,11 @@ +pragma experimental ABIEncoderV2; +contract C { + function f(uint256[][] calldata a) external returns (uint) { + return 42; + } +} +// ==== +// EVMVersion: >=byzantium +// revertStrings: debug +// ---- +// f(uint256[][]): 0x20, 1 -> FAILURE, hex"08c379a0", 0x20, 43, "ABI decoding: invalid calldata a", "rray stride" diff --git a/test/libsolidity/semanticTests/revertStrings/calldata_array_dynamic_static_short_decode.sol b/test/libsolidity/semanticTests/revertStrings/calldata_array_dynamic_static_short_decode.sol new file mode 100644 index 000000000..9f9296d2a --- /dev/null +++ b/test/libsolidity/semanticTests/revertStrings/calldata_array_dynamic_static_short_decode.sol @@ -0,0 +1,12 @@ +pragma experimental ABIEncoderV2; +contract C { + function f(uint256[][2][] calldata x) external returns (uint256) { + x[0]; + return 23; + } +} +// ==== +// EVMVersion: >=byzantium +// revertStrings: debug +// ---- +// f(uint256[][2][]): 0x20, 0x01, 0x20, 0x00 -> FAILURE, hex"08c379a0", 0x20, 28, "Invalid calldata tail offset" diff --git a/test/libsolidity/semanticTests/revertStrings/calldata_array_dynamic_static_short_reencode.sol b/test/libsolidity/semanticTests/revertStrings/calldata_array_dynamic_static_short_reencode.sol new file mode 100644 index 000000000..7aac0f7f8 --- /dev/null +++ b/test/libsolidity/semanticTests/revertStrings/calldata_array_dynamic_static_short_reencode.sol @@ -0,0 +1,14 @@ +pragma experimental ABIEncoderV2; +contract C { + function f(uint256[][2][] calldata x) external returns (uint256) { + return 42; + } + function g(uint256[][2][] calldata x) external returns (uint256) { + return this.f(x); + } +} +// ==== +// EVMVersion: >=byzantium +// revertStrings: debug +// ---- +// g(uint256[][2][]): 0x20, 0x01, 0x20, 0x00 -> FAILURE, hex"08c379a0", 0x20, 30, "Invalid calldata access offset" diff --git a/test/libsolidity/semanticTests/revertStrings/calldata_array_invalid_length.sol b/test/libsolidity/semanticTests/revertStrings/calldata_array_invalid_length.sol new file mode 100644 index 000000000..3b68bacea --- /dev/null +++ b/test/libsolidity/semanticTests/revertStrings/calldata_array_invalid_length.sol @@ -0,0 +1,11 @@ +pragma experimental ABIEncoderV2; +contract C { + function f(uint256[][] calldata x) external returns (uint256) { + return x[0].length; + } +} +// ==== +// EVMVersion: >=byzantium +// revertStrings: debug +// ---- +// f(uint256[][]): 0x20, 1, 0x20, 0x0100000000000000000000 -> FAILURE, hex"08c379a0", 0x20, 28, "Invalid calldata tail length" diff --git a/test/libsolidity/semanticTests/revertStrings/calldata_arrays_too_large.sol b/test/libsolidity/semanticTests/revertStrings/calldata_arrays_too_large.sol new file mode 100644 index 000000000..52809a0b2 --- /dev/null +++ b/test/libsolidity/semanticTests/revertStrings/calldata_arrays_too_large.sol @@ -0,0 +1,11 @@ +pragma experimental ABIEncoderV2; +contract C { + function f(uint a, uint[] calldata b, uint c) external pure returns (uint) { + return 7; + } +} +// ==== +// EVMVersion: >=byzantium +// revertStrings: debug +// ---- +// f(uint256,uint256[],uint256): 6, 0x60, 9, 0x1000000000000000000000000000000000000000000000000000000000000002, 1, 2 -> FAILURE, hex"08c379a0", 0x20, 43, "ABI decoding: invalid calldata a", "rray length" diff --git a/test/libsolidity/semanticTests/revertStrings/calldata_too_short.sol b/test/libsolidity/semanticTests/revertStrings/calldata_too_short.sol new file mode 100644 index 000000000..fea9fe72c --- /dev/null +++ b/test/libsolidity/semanticTests/revertStrings/calldata_too_short.sol @@ -0,0 +1,11 @@ +contract C { + function d(bytes memory _data) public pure returns (uint8) { + return abi.decode(_data, (uint8)); + } +} +// ==== +// EVMVersion: >=byzantium +// revertStrings: debug +// ABIEncoderV1Only: true +// ---- +// d(bytes): 0x20, 0x01, 0x0000000000000000000000000000000000000000000000000000000000000000 -> FAILURE, hex"08c379a0", 0x20, 18, "Calldata too short" diff --git a/test/libsolidity/semanticTests/revertStrings/called_contract_has_code.sol b/test/libsolidity/semanticTests/revertStrings/called_contract_has_code.sol new file mode 100644 index 000000000..b21f85cc8 --- /dev/null +++ b/test/libsolidity/semanticTests/revertStrings/called_contract_has_code.sol @@ -0,0 +1,12 @@ +contract C { + function f() external {} + function g() external { + C c = C(0x0000000000000000000000000000000000000000000000000000000000000000); + c.f(); + } +} +// ==== +// EVMVersion: >=byzantium +// revertStrings: debug +// ---- +// g() -> FAILURE, hex"08c379a0", 0x20, 37, "Target contract does not contain", " code" diff --git a/test/libsolidity/semanticTests/revertStrings/enum.sol b/test/libsolidity/semanticTests/revertStrings/enum.sol new file mode 100644 index 000000000..db3f9f7e2 --- /dev/null +++ b/test/libsolidity/semanticTests/revertStrings/enum.sol @@ -0,0 +1,12 @@ +contract C { + enum E {X, Y} + function f(E[] calldata arr) external { + arr[1]; + } +} +// ==== +// EVMVersion: >=byzantium +// revertStrings: debug +// ABIEncoderV1Only: true +// ---- +// f(uint8[]): 0x20, 2, 3, 3 -> FAILURE, hex"08c379a0", 0x20, 17, "Enum out of range" diff --git a/test/libsolidity/semanticTests/revertStrings/ether_non_payable_function.sol b/test/libsolidity/semanticTests/revertStrings/ether_non_payable_function.sol new file mode 100644 index 000000000..ffaea6878 --- /dev/null +++ b/test/libsolidity/semanticTests/revertStrings/ether_non_payable_function.sol @@ -0,0 +1,9 @@ +contract C { + function f() public {} +} +// ==== +// EVMVersion: >=byzantium +// revertStrings: debug +// ---- +// f(), 1 ether -> FAILURE, hex"08c379a0", 0x20, 34, "Ether sent to non-payable functi", "on" +// () -> FAILURE, hex"08c379a0", 0x20, 53, "Contract does not have fallback ", "nor receive functions" diff --git a/test/libsolidity/semanticTests/revertStrings/function_entry_checks.sol b/test/libsolidity/semanticTests/revertStrings/function_entry_checks.sol new file mode 100644 index 000000000..4072db952 --- /dev/null +++ b/test/libsolidity/semanticTests/revertStrings/function_entry_checks.sol @@ -0,0 +1,9 @@ +contract C { + function t(uint) public pure {} +} +// ==== +// EVMVersion: >=byzantium +// compileViaYul: true +// revertStrings: debug +// ---- +// t(uint256) -> FAILURE, hex"08c379a0", 0x20, 34, "ABI decoding: tuple data too sho", "rt" diff --git a/test/libsolidity/semanticTests/revertStrings/invalid_abi_decoding_calldata_v1.sol b/test/libsolidity/semanticTests/revertStrings/invalid_abi_decoding_calldata_v1.sol new file mode 100644 index 000000000..3468bfc07 --- /dev/null +++ b/test/libsolidity/semanticTests/revertStrings/invalid_abi_decoding_calldata_v1.sol @@ -0,0 +1,13 @@ +contract C { + function d(bytes memory _data) public pure returns (uint8) { + return abi.decode(_data, (uint8)); + } +} +// ==== +// EVMVersion: >=byzantium +// revertStrings: debug +// ABIEncoderV1Only: true +// ---- +// d(bytes): 0x20, 0x20, 0x0000000000000000000000000000000000000000000000000000000000000000 -> 0 +// d(bytes): 0x100, 0x20, 0x0000000000000000000000000000000000000000000000000000000000000000 -> FAILURE, hex"08c379a0", 0x20, 43, "ABI calldata decoding: invalid h", "ead pointer" +// d(bytes): 0x20, 0x100, 0x0000000000000000000000000000000000000000000000000000000000000000 -> FAILURE, hex"08c379a0", 0x20, 43, "ABI calldata decoding: invalid d", "ata pointer" diff --git a/test/libsolidity/semanticTests/revertStrings/invalid_abi_decoding_memory_v1.sol b/test/libsolidity/semanticTests/revertStrings/invalid_abi_decoding_memory_v1.sol new file mode 100644 index 000000000..68981a3cb --- /dev/null +++ b/test/libsolidity/semanticTests/revertStrings/invalid_abi_decoding_memory_v1.sol @@ -0,0 +1,20 @@ +contract C { + function dyn(uint ptr, uint start, uint x) public returns (bytes memory a) { + assembly { + mstore(0, start) + mstore(start, add(start, 1)) + return(ptr, x) + } + } + function f(uint ptr, uint start, uint x) public returns (bool) { + this.dyn(ptr, start, x); + return true; + } +} +// ==== +// EVMVersion: >=byzantium +// revertStrings: debug +// ABIEncoderV1Only: true +// ---- +// f(uint256,uint256,uint256): 0, 0x200, 0x60 -> FAILURE, hex"08c379a0", 0x20, 39, "ABI memory decoding: invalid dat", "a start" +// f(uint256,uint256,uint256): 0, 0x20, 0x60 -> FAILURE, hex"08c379a0", 0x20, 40, "ABI memory decoding: invalid dat", "a length" diff --git a/test/libsolidity/semanticTests/revertStrings/library_non_view_call.sol b/test/libsolidity/semanticTests/revertStrings/library_non_view_call.sol new file mode 100644 index 000000000..1a079a04a --- /dev/null +++ b/test/libsolidity/semanticTests/revertStrings/library_non_view_call.sol @@ -0,0 +1,16 @@ +library L { + function g() external {} +} +contract C { + function f() public returns (bytes memory) { + (bool success, bytes memory result) = address(L).call(abi.encodeWithSignature("g()")); + assert(!success); + return result; + } +} +// ==== +// EVMVersion: >=byzantium +// revertStrings: debug +// ---- +// library: L +// f() -> 32, 132, 3963877391197344453575983046348115674221700746820753546331534351508065746944, 862718293348820473429344482784628181556388621521298319395315527974912, 1518017211910606845658622928256476421055725129218887721595913401102969, 14649601406562900601407788686537400806574002225747213573947654179243427889152, 0 diff --git a/test/libsolidity/semanticTests/revertStrings/short_input_array.sol b/test/libsolidity/semanticTests/revertStrings/short_input_array.sol new file mode 100644 index 000000000..1a80acc26 --- /dev/null +++ b/test/libsolidity/semanticTests/revertStrings/short_input_array.sol @@ -0,0 +1,9 @@ +pragma experimental ABIEncoderV2; +contract C { + function f(uint[] memory a) public pure returns (uint) { return 7; } +} +// ==== +// EVMVersion: >=byzantium +// revertStrings: debug +// ---- +// f(uint256[]): 0x20, 1 -> FAILURE, hex"08c379a0", 0x20, 43, "ABI decoding: invalid calldata a", "rray stride" diff --git a/test/libsolidity/semanticTests/revertStrings/short_input_bytes.sol b/test/libsolidity/semanticTests/revertStrings/short_input_bytes.sol new file mode 100644 index 000000000..f361da7b6 --- /dev/null +++ b/test/libsolidity/semanticTests/revertStrings/short_input_bytes.sol @@ -0,0 +1,9 @@ +pragma experimental ABIEncoderV2; +contract C { + function e(bytes memory a) public pure returns (uint) { return 7; } +} +// ==== +// EVMVersion: >=byzantium +// revertStrings: debug +// ---- +// e(bytes): 0x20, 7 -> FAILURE, hex"08c379a0", 0x20, 39, "ABI decoding: invalid byte array", " length" diff --git a/test/libsolidity/semanticTests/revertStrings/transfer.sol b/test/libsolidity/semanticTests/revertStrings/transfer.sol new file mode 100644 index 000000000..fcf971a5c --- /dev/null +++ b/test/libsolidity/semanticTests/revertStrings/transfer.sol @@ -0,0 +1,27 @@ +contract A { + receive() external payable { + revert("no_receive"); + } +} + +contract C { + A a = new A(); + receive() external payable {} + function f() public { + address(a).transfer(1 wei); + } + function h() public { + address(a).transfer(100 ether); + } + function g() public view returns (uint) { + return address(this).balance; + } +} +// ==== +// EVMVersion: >=byzantium +// revertStrings: debug +// ---- +// (), 10 ether -> +// g() -> 10 +// f() -> FAILURE, hex"08c379a0", 0x20, 10, "no_receive" +// h() -> FAILURE diff --git a/test/libsolidity/semanticTests/revertStrings/unknown_sig_no_fallback.sol b/test/libsolidity/semanticTests/revertStrings/unknown_sig_no_fallback.sol new file mode 100644 index 000000000..62b245885 --- /dev/null +++ b/test/libsolidity/semanticTests/revertStrings/unknown_sig_no_fallback.sol @@ -0,0 +1,8 @@ +contract A { + receive () external payable {} +} +// ==== +// EVMVersion: >=byzantium +// revertStrings: debug +// ---- +// (): hex"00" -> FAILURE, hex"08c379a0", 0x20, 41, "Unknown signature and no fallbac", "k defined"