mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Add reason string for internal reverts
This commit is contained in:
parent
e8eb1f2d14
commit
36928c7a35
@ -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:
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -180,11 +180,12 @@ string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory)
|
||||
|
||||
Whiskers templ(R"(
|
||||
function <functionName>(headStart, dataEnd) <arrow> <valueReturnParams> {
|
||||
if slt(sub(dataEnd, headStart), <minimumSize>) { revert(0, 0) }
|
||||
if slt(sub(dataEnd, headStart), <minimumSize>) { <revertString> }
|
||||
<decodeElements>
|
||||
}
|
||||
)");
|
||||
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 := <load>(add(headStart, <pos>))
|
||||
if gt(offset, 0xffffffffffffffff) { revert(0, 0) }
|
||||
if gt(offset, 0xffffffffffffffff) { <revertString> }
|
||||
<values> := <abiDecode>(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, <maxLength>) { revert(0, 0) }
|
||||
if gt(length, <maxLength>) { <revertString> }
|
||||
length := mul(length, <stride>)
|
||||
)")
|
||||
("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"(
|
||||
// <readableTypeName>
|
||||
function <functionName>(offset, end) -> array {
|
||||
if iszero(slt(add(offset, 0x1f), end)) { revert(0, 0) }
|
||||
if iszero(slt(add(offset, 0x1f), end)) { <revertString> }
|
||||
let length := <retrieveLength>
|
||||
array := <allocate>(<allocationSize>(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"(
|
||||
// <readableTypeName>
|
||||
function <functionName>(offset, end) -> arrayPos, length {
|
||||
if iszero(slt(add(offset, 0x1f), end)) { revert(0, 0) }
|
||||
if iszero(slt(add(offset, 0x1f), end)) { <revertStringOffset> }
|
||||
length := calldataload(offset)
|
||||
if gt(length, 0xffffffffffffffff) { revert(0, 0) }
|
||||
if gt(length, 0xffffffffffffffff) { <revertStringLength> }
|
||||
arrayPos := add(offset, 0x20)
|
||||
if gt(add(arrayPos, mul(length, <stride>)), end) { revert(0, 0) }
|
||||
if gt(add(arrayPos, mul(length, <stride>)), end) { <revertStringPos> }
|
||||
}
|
||||
)";
|
||||
else
|
||||
@ -1196,10 +1208,14 @@ string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type)
|
||||
// <readableTypeName>
|
||||
function <functionName>(offset, end) -> arrayPos {
|
||||
arrayPos := offset
|
||||
if gt(add(arrayPos, mul(<length>, <stride>)), end) { revert(0, 0) }
|
||||
if gt(add(arrayPos, mul(<length>, <stride>)), end) { <revertStringPos> }
|
||||
}
|
||||
)";
|
||||
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 <functionName>(offset, end) -> array {
|
||||
if iszero(slt(add(offset, 0x1f), end)) { revert(0, 0) }
|
||||
if iszero(slt(add(offset, 0x1f), end)) { <revertStringOffset> }
|
||||
let length := <load>(offset)
|
||||
array := <allocate>(<allocationSize>(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) { <revertStringLength> }
|
||||
<copyToMemFun>(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"(
|
||||
// <readableTypeName>
|
||||
function <functionName>(offset, end) -> value {
|
||||
if slt(sub(end, offset), <minimumSize>) { revert(0, 0) }
|
||||
if slt(sub(end, offset), <minimumSize>) { <revertString> }
|
||||
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"(
|
||||
// <readableTypeName>
|
||||
function <functionName>(headStart, end) -> value {
|
||||
if slt(sub(end, headStart), <minimumSize>) { revert(0, 0) }
|
||||
if slt(sub(end, headStart), <minimumSize>) { <revertString> }
|
||||
value := <allocate>(<memorySize>)
|
||||
<#members>
|
||||
{
|
||||
@ -1287,6 +1308,8 @@ string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fr
|
||||
</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());
|
||||
@ -1305,7 +1328,7 @@ string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fr
|
||||
dynamic ?
|
||||
R"(
|
||||
let offset := <load>(add(headStart, <pos>))
|
||||
if gt(offset, 0xffffffffffffffff) { revert(0, 0) }
|
||||
if gt(offset, 0xffffffffffffffff) { <revertString> }
|
||||
mstore(add(value, <memoryOffset>), <abiDecode>(add(headStart, offset), end))
|
||||
)" :
|
||||
R"(
|
||||
@ -1313,6 +1336,8 @@ string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fr
|
||||
mstore(add(value, <memoryOffset>), <abiDecode>(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 <functionName>(base_ref, ptr) -> <return> {
|
||||
let rel_offset_of_tail := calldataload(ptr)
|
||||
if iszero(slt(rel_offset_of_tail, sub(sub(calldatasize(), base_ref), sub(<neededLength>, 1)))) { revert(0, 0) }
|
||||
if iszero(slt(rel_offset_of_tail, sub(sub(calldatasize(), base_ref), sub(<neededLength>, 1)))) { <revertStringOffset> }
|
||||
value := add(rel_offset_of_tail, base_ref)
|
||||
<handleLength>
|
||||
}
|
||||
@ -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, <calldataStride>))) { revert(0, 0) }
|
||||
)")("calldataStride", toCompactHexWithPrefix(arrayType->calldataStride())).render());
|
||||
if gt(length, 0xffffffffffffffff) { <revertStringLength> }
|
||||
if sgt(base_ref, sub(calldatasize(), mul(length, <calldataStride>))) { <revertStringStride> }
|
||||
)")
|
||||
("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);
|
||||
}
|
||||
|
@ -25,6 +25,8 @@
|
||||
#include <libsolidity/codegen/MultiUseYulFunctionCollector.h>
|
||||
#include <libsolidity/codegen/YulUtilFunctions.h>
|
||||
|
||||
#include <libsolidity/interface/DebugSettings.h>
|
||||
|
||||
#include <liblangutil/EVMVersion.h>
|
||||
|
||||
#include <functional>
|
||||
@ -55,11 +57,13 @@ class ABIFunctions
|
||||
public:
|
||||
explicit ABIFunctions(
|
||||
langutil::EVMVersion _evmVersion,
|
||||
RevertStrings _revertStrings,
|
||||
std::shared_ptr<MultiUseYulFunctionCollector> _functionCollector = std::make_shared<MultiUseYulFunctionCollector>()
|
||||
):
|
||||
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<MultiUseYulFunctionCollector> m_functionCollector;
|
||||
std::set<std::string> m_externallyUsedFunctions;
|
||||
YulUtilFunctions m_utils;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -37,6 +37,8 @@
|
||||
#include <libyul/Object.h>
|
||||
#include <libyul/YulString.h>
|
||||
|
||||
#include <libsolutil/Whiskers.h>
|
||||
|
||||
#include <liblangutil/ErrorReporter.h>
|
||||
#include <liblangutil/Scanner.h>
|
||||
#include <liblangutil/SourceReferenceFormatter.h>
|
||||
@ -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<ContractDefinition const*>::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());
|
||||
|
@ -27,6 +27,7 @@
|
||||
#include <libsolidity/ast/Types.h>
|
||||
#include <libsolidity/codegen/ABIFunctions.h>
|
||||
|
||||
#include <libsolidity/interface/DebugSettings.h>
|
||||
#include <libsolidity/interface/OptimiserSettings.h>
|
||||
|
||||
#include <libevmasm/Assembly.h>
|
||||
@ -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<evmasm::Assembly>()),
|
||||
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<ExperimentalFeature> m_experimentalFeatures;
|
||||
/// Other already compiled contracts to be used in contract creation calls.
|
||||
|
@ -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(<neededLength>, 1)))) { revert(0, 0) }
|
||||
if iszero(slt(rel_offset_of_tail, sub(sub(calldatasize(), base_ref), sub(<neededLength>, 1)))) { <revertString> }
|
||||
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: <absolute_offset_of_tail> <garbage>
|
||||
|
||||
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) { <revertString> }
|
||||
if sgt(base_ref, sub(calldatasize(), mul(length, <calldataStride>))) { revert(0, 0) }
|
||||
})")("calldataStride", toCompactHexWithPrefix(calldataStride)).render(),
|
||||
})")
|
||||
("calldataStride", toCompactHexWithPrefix(calldataStride))
|
||||
("revertString", m_context.revertReasonIfDebug("Invalid calldata tail length"))
|
||||
.render(),
|
||||
{"base_ref", "length"}
|
||||
);
|
||||
// stack layout: <absolute_offset_of_tail> <length>
|
||||
@ -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, <encodedSize>) { <revertString> }
|
||||
})");
|
||||
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) { <revertStringPointer> }
|
||||
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) { <revertStringStart> }
|
||||
let array_length := mload(ptr)
|
||||
if or(
|
||||
gt(array_length, 0x100000000),
|
||||
gt(add(array_data_start, mul(array_length, <item_size>)), input_end)
|
||||
) { revert(0, 0) }
|
||||
) { <revertStringLength> }
|
||||
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) { <revertString> }
|
||||
})")
|
||||
// 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> }
|
||||
})")
|
||||
("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> }
|
||||
})")
|
||||
("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;
|
||||
|
@ -24,6 +24,7 @@
|
||||
|
||||
#include <libsolidity/ast/ASTForward.h>
|
||||
#include <libsolidity/ast/TypeProvider.h>
|
||||
#include <libsolidity/interface/DebugSettings.h>
|
||||
#include <libsolidity/codegen/CompilerContext.h>
|
||||
#include <libsolidity/codegen/CompilerContext.h>
|
||||
|
||||
@ -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();
|
||||
|
@ -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<ASTPointer<TryCatchClause>> 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);
|
||||
|
@ -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;
|
||||
|
@ -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) { <revertStringStartEnd> }
|
||||
if gt(sliceEnd, length) { <revertStringEndLength> }
|
||||
|
||||
offset := add(offset, mul(sliceStart, <stride>))
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -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<LValue> m_currentLValue;
|
||||
|
@ -134,7 +134,7 @@ string YulUtilFunctions::requireOrAssertFunction(bool _assert, Type const* _mess
|
||||
FixedHash<hashHeaderSize>(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, <sig>)
|
||||
mstore(4, 0x20)
|
||||
mstore(add(4, 0x20), <length>)
|
||||
let reasonPos := add(4, 0x40)
|
||||
<#word>
|
||||
mstore(add(reasonPos, <offset>), <wordValue>)
|
||||
</word>
|
||||
revert(0, add(reasonPos, <end>))
|
||||
})");
|
||||
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<map<string, string>> 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);
|
||||
}
|
||||
|
@ -24,6 +24,8 @@
|
||||
|
||||
#include <libsolidity/codegen/MultiUseYulFunctionCollector.h>
|
||||
|
||||
#include <libsolidity/interface/DebugSettings.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@ -44,9 +46,11 @@ class YulUtilFunctions
|
||||
public:
|
||||
explicit YulUtilFunctions(
|
||||
langutil::EVMVersion _evmVersion,
|
||||
RevertStrings _revertStrings,
|
||||
std::shared_ptr<MultiUseYulFunctionCollector> _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<MultiUseYulFunctionCollector> m_functionCollector;
|
||||
};
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -21,6 +21,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <libsolidity/interface/OptimiserSettings.h>
|
||||
#include <libsolidity/interface/DebugSettings.h>
|
||||
|
||||
#include <libsolidity/codegen/MultiUseYulFunctionCollector.h>
|
||||
|
||||
@ -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<MultiUseYulFunctionCollector>())
|
||||
{}
|
||||
@ -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<ContractDefinition const*> m_inheritanceHierarchy;
|
||||
std::map<VariableDeclaration const*, std::string> m_localVariables;
|
||||
|
@ -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())
|
||||
|
@ -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
|
||||
|
@ -534,7 +534,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
|
||||
{
|
||||
auto const& event = dynamic_cast<EventDefinition const&>(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<string> 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"(
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -661,10 +661,10 @@ boost::variant<StandardCompiler::InputsAndSettings, Json::Value> StandardCompile
|
||||
std::optional<RevertStrings> 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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -169,26 +169,32 @@ BOOST_AUTO_TEST_CASE(location_test)
|
||||
if (solidity::test::CommonOptions::get().optimize)
|
||||
locations =
|
||||
vector<SourceLocation>(4, SourceLocation{2, 82, sourceCode}) +
|
||||
vector<SourceLocation>(1, SourceLocation{8, 17, codegenCharStream}) +
|
||||
vector<SourceLocation>(3, SourceLocation{5, 7, codegenCharStream}) +
|
||||
vector<SourceLocation>(1, SourceLocation{30, 31, codegenCharStream}) +
|
||||
vector<SourceLocation>(1, SourceLocation{5, 14, codegenCharStream}) +
|
||||
vector<SourceLocation>(3, SourceLocation{2, 4, codegenCharStream}) +
|
||||
vector<SourceLocation>(1, SourceLocation{27, 28, codegenCharStream}) +
|
||||
vector<SourceLocation>(1, SourceLocation{20, 32, codegenCharStream}) +
|
||||
vector<SourceLocation>(1, SourceLocation{5, 7, codegenCharStream}) +
|
||||
vector<SourceLocation>(19, SourceLocation{2, 82, sourceCode}) +
|
||||
vector<SourceLocation>(1, SourceLocation{24, 25, codegenCharStream}) +
|
||||
vector<SourceLocation>(1, SourceLocation{17, 29, codegenCharStream}) +
|
||||
vector<SourceLocation>(1, SourceLocation{2, 4, codegenCharStream}) +
|
||||
vector<SourceLocation>(16, SourceLocation{2, 82, sourceCode}) +
|
||||
vector<SourceLocation>(1, SourceLocation{12, 13, codegenCharStream}) +
|
||||
vector<SourceLocation>(1, SourceLocation{9, 10, codegenCharStream}) +
|
||||
vector<SourceLocation>(1, SourceLocation{2, 14, codegenCharStream}) +
|
||||
vector<SourceLocation>(21, SourceLocation{20, 79, sourceCode}) +
|
||||
vector<SourceLocation>(1, SourceLocation{72, 74, sourceCode}) +
|
||||
vector<SourceLocation>(2, SourceLocation{20, 79, sourceCode});
|
||||
else
|
||||
locations =
|
||||
vector<SourceLocation>(4, SourceLocation{2, 82, sourceCode}) +
|
||||
vector<SourceLocation>(1, SourceLocation{8, 17, codegenCharStream}) +
|
||||
vector<SourceLocation>(3, SourceLocation{5, 7, codegenCharStream}) +
|
||||
vector<SourceLocation>(1, SourceLocation{30, 31, codegenCharStream}) +
|
||||
vector<SourceLocation>(1, SourceLocation{5, 14, codegenCharStream}) +
|
||||
vector<SourceLocation>(3, SourceLocation{2, 4, codegenCharStream}) +
|
||||
vector<SourceLocation>(1, SourceLocation{27, 28, codegenCharStream}) +
|
||||
vector<SourceLocation>(1, SourceLocation{20, 32, codegenCharStream}) +
|
||||
vector<SourceLocation>(1, SourceLocation{5, 7, codegenCharStream}) +
|
||||
vector<SourceLocation>(hasShifts ? 19 : 20, SourceLocation{2, 82, sourceCode}) +
|
||||
vector<SourceLocation>(1, SourceLocation{24, 25, codegenCharStream}) +
|
||||
vector<SourceLocation>(1, SourceLocation{17, 29, codegenCharStream}) +
|
||||
vector<SourceLocation>(1, SourceLocation{2, 4, codegenCharStream}) +
|
||||
vector<SourceLocation>(hasShifts ? 16 : 17, SourceLocation{2, 82, sourceCode}) +
|
||||
vector<SourceLocation>(1, SourceLocation{12, 13, codegenCharStream}) +
|
||||
vector<SourceLocation>(1, SourceLocation{9, 10, codegenCharStream}) +
|
||||
vector<SourceLocation>(1, SourceLocation{2, 14, codegenCharStream}) +
|
||||
vector<SourceLocation>(24, SourceLocation{20, 79, sourceCode}) +
|
||||
vector<SourceLocation>(1, SourceLocation{49, 58, sourceCode}) +
|
||||
vector<SourceLocation>(1, SourceLocation{72, 74, sourceCode}) +
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -27,6 +27,7 @@
|
||||
#include <test/ExecutionFramework.h>
|
||||
|
||||
#include <libsolidity/interface/CompilerStack.h>
|
||||
#include <libsolidity/interface/DebugSettings.h>
|
||||
|
||||
#include <libyul/AssemblyStack.h>
|
||||
|
||||
@ -67,6 +68,8 @@ public:
|
||||
protected:
|
||||
solidity::frontend::CompilerStack m_compiler;
|
||||
bool m_compileViaYul = false;
|
||||
RevertStrings m_revertStrings = RevertStrings::Default;
|
||||
|
||||
};
|
||||
|
||||
} // end namespaces
|
||||
|
@ -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());
|
||||
|
||||
|
@ -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\"},"
|
||||
|
@ -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"
|
15
test/libsolidity/semanticTests/revertStrings/bubble.sol
Normal file
15
test/libsolidity/semanticTests/revertStrings/bubble.sol
Normal file
@ -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"
|
@ -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"
|
@ -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"
|
@ -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"
|
@ -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"
|
@ -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"
|
@ -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"
|
@ -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"
|
12
test/libsolidity/semanticTests/revertStrings/enum.sol
Normal file
12
test/libsolidity/semanticTests/revertStrings/enum.sol
Normal file
@ -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"
|
@ -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"
|
@ -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"
|
@ -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"
|
@ -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"
|
@ -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
|
@ -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"
|
@ -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"
|
27
test/libsolidity/semanticTests/revertStrings/transfer.sol
Normal file
27
test/libsolidity/semanticTests/revertStrings/transfer.sol
Normal file
@ -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
|
@ -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"
|
Loading…
Reference in New Issue
Block a user