Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add abi.encodeError #14974

Closed
wants to merge 15 commits into from
145 changes: 144 additions & 1 deletion libsolidity/analysis/TypeChecker.cpp
Expand Up @@ -2203,6 +2203,7 @@ void TypeChecker::typeCheckABIEncodeFunctions(
_functionType->kind() == FunctionType::Kind::ABIEncodePacked ||
_functionType->kind() == FunctionType::Kind::ABIEncodeWithSelector ||
_functionType->kind() == FunctionType::Kind::ABIEncodeCall ||
_functionType->kind() == FunctionType::Kind::ABIEncodeError ||
_functionType->kind() == FunctionType::Kind::ABIEncodeWithSignature,
"ABI function has unexpected FunctionType::Kind."
);
Expand All @@ -2227,12 +2228,17 @@ void TypeChecker::typeCheckABIEncodeFunctions(
// Perform standard function call type checking
typeCheckFunctionGeneralChecks(_functionCall, _functionType);

// No further generic checks needed as we do a precise check for ABIEncodeCall
// No further generic checks needed as we do a precise check for ABIEncodeCall and ABIEncodeError
if (_functionType->kind() == FunctionType::Kind::ABIEncodeCall)
{
typeCheckABIEncodeCallFunction(_functionCall);
return;
}
else if (_functionType->kind() == FunctionType::Kind::ABIEncodeError)
Amxx marked this conversation as resolved.
Show resolved Hide resolved
{
typeCheckABIEncodeErrorFunction(_functionCall);
return;
}

// Check additional arguments for variadic functions
std::vector<ASTPointer<Expression const>> const& arguments = _functionCall.arguments();
Expand Down Expand Up @@ -2445,6 +2451,142 @@ void TypeChecker::typeCheckABIEncodeCallFunction(FunctionCall const& _functionCa
}
}

void TypeChecker::typeCheckABIEncodeErrorFunction(FunctionCall const& _functionCall)
{
std::vector<ASTPointer<Expression const>> const& arguments = _functionCall.arguments();

// Expecting first argument to be the function pointer and second to be a tuple.
if (arguments.size() != 2)
{
m_errorReporter.typeError(
6220_error,
_functionCall.location(),
"Expected two arguments: a custom error followed by a tuple."
);
return;
}

FunctionType const* externalFunctionType = nullptr;
if (auto const functionPointerType = dynamic_cast<FunctionTypePointer>(type(*arguments.front())))
{
// this cannot be a library function, that is checked below
externalFunctionType = functionPointerType->asExternallyCallableFunction(false);
solAssert(externalFunctionType->kind() == functionPointerType->kind());
}
else
{
m_errorReporter.typeError(
5512_error,
arguments.front()->location(),
"Expected first argument to be a custom error, not \"" +
type(*arguments.front())->humanReadableName() +
"\"."
);
return;
}

if (externalFunctionType->kind() != FunctionType::Kind::Error)
{
std::string msg = "Expected an error type.";

switch (externalFunctionType->kind())
{
case FunctionType::Kind::Internal:
case FunctionType::Kind::External:
case FunctionType::Kind::Declaration:
msg += " Cannot use functions for abi.encodeError.";
break;
case FunctionType::Kind::DelegateCall:
msg += " Cannot use library functions for abi.encodeError.";
break;
case FunctionType::Kind::Creation:
msg += " Provided creation function.";
break;
case FunctionType::Kind::Event:
msg += " Cannot use events for abi.encodeError.";
break;
default:
msg += " Cannot use special function.";
}

SecondarySourceLocation ssl{};

if (externalFunctionType->hasDeclaration())
{
ssl.append("Function is declared here:", externalFunctionType->declaration().location());
// add something to message?
}

m_errorReporter.typeError(3510_error, arguments[0]->location(), ssl, msg);
return;
}
solAssert(!externalFunctionType->takesArbitraryParameters(), "Function must have fixed parameters.");
// Tuples with only one component become that component
std::vector<ASTPointer<Expression const>> callArguments;

auto const* tupleType = dynamic_cast<TupleType const*>(type(*arguments[1]));
if (tupleType)
{
if (TupleExpression const* argumentTuple = dynamic_cast<TupleExpression const*>(arguments[1].get()))
callArguments = decltype(callArguments){argumentTuple->components().begin(), argumentTuple->components().end()};
Amxx marked this conversation as resolved.
Show resolved Hide resolved
else
{
m_errorReporter.typeError(
9063_error,
arguments[1]->location(),
"Expected an inline tuple, not an expression of a tuple type."
);
return;
}
}
else
callArguments.push_back(arguments[1]);

if (externalFunctionType->parameterTypes().size() != callArguments.size())
{
if (tupleType)
m_errorReporter.typeError(
7789_error,
_functionCall.location(),
"Expected " +
std::to_string(externalFunctionType->parameterTypes().size()) +
" instead of " +
std::to_string(callArguments.size()) +
" components for the tuple parameter."
);
else
m_errorReporter.typeError(
7516_error,
_functionCall.location(),
"Expected a tuple with " +
std::to_string(externalFunctionType->parameterTypes().size()) +
" components instead of a single non-tuple parameter."
);
}

// Use min() to check as much as we can before failing fatally
size_t const numParameters = std::min(callArguments.size(), externalFunctionType->parameterTypes().size());

for (size_t i = 0; i < numParameters; i++)
{
Type const& argType = *type(*callArguments[i]);
BoolResult result = argType.isImplicitlyConvertibleTo(*externalFunctionType->parameterTypes()[i]);
if (!result)
m_errorReporter.typeError(
5408_error,
callArguments[i]->location(),
"Cannot implicitly convert component at position " +
std::to_string(i) +
" from \"" +
argType.humanReadableName() +
"\" to \"" +
externalFunctionType->parameterTypes()[i]->humanReadableName() +
"\"" +
(result.message().empty() ? "." : ": " + result.message())
);
}
}


void TypeChecker::typeCheckStringConcatFunction(
FunctionCall const& _functionCall,
Expand Down Expand Up @@ -2903,6 +3045,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
case FunctionType::Kind::ABIEncodeWithSelector:
case FunctionType::Kind::ABIEncodeWithSignature:
case FunctionType::Kind::ABIEncodeCall:
case FunctionType::Kind::ABIEncodeError:
{
typeCheckABIEncodeFunctions(_functionCall, functionType);
returnTypes = functionType->returnParameterTypes();
Expand Down
3 changes: 3 additions & 0 deletions libsolidity/analysis/TypeChecker.h
Expand Up @@ -113,6 +113,9 @@ class TypeChecker: private ASTConstVisitor
/// Performs checks specific to the ABI encode functions of type ABIEncodeCall
void typeCheckABIEncodeCallFunction(FunctionCall const& _functionCall);

/// Performs checks specific to the ABI encode functions of type ABIEncodeError
void typeCheckABIEncodeErrorFunction(FunctionCall const& _functionCall);

/// Performs general checks and checks specific to string concat function call
void typeCheckStringConcatFunction(
FunctionCall const& _functionCall,
Expand Down
1 change: 1 addition & 0 deletions libsolidity/analysis/ViewPureChecker.cpp
Expand Up @@ -390,6 +390,7 @@ void ViewPureChecker::endVisit(MemberAccess const& _memberAccess)
{MagicType::Kind::ABI, "encodePacked"},
{MagicType::Kind::ABI, "encodeWithSelector"},
{MagicType::Kind::ABI, "encodeCall"},
{MagicType::Kind::ABI, "encodeError"},
{MagicType::Kind::ABI, "encodeWithSignature"},
{MagicType::Kind::Message, "data"},
{MagicType::Kind::Message, "sig"},
Expand Down
12 changes: 12 additions & 0 deletions libsolidity/ast/Types.cpp
Expand Up @@ -3073,6 +3073,7 @@ std::string FunctionType::richIdentifier() const
case Kind::ABIEncodePacked: id += "abiencodepacked"; break;
case Kind::ABIEncodeWithSelector: id += "abiencodewithselector"; break;
case Kind::ABIEncodeCall: id += "abiencodecall"; break;
case Kind::ABIEncodeError: id += "abiencodeerror"; break;
case Kind::ABIEncodeWithSignature: id += "abiencodewithsignature"; break;
case Kind::ABIDecode: id += "abidecode"; break;
case Kind::BlobHash: id += "blobhash"; break;
Expand Down Expand Up @@ -3661,6 +3662,7 @@ bool FunctionType::isPure() const
m_kind == Kind::ABIEncodePacked ||
m_kind == Kind::ABIEncodeWithSelector ||
m_kind == Kind::ABIEncodeCall ||
m_kind == Kind::ABIEncodeError ||
m_kind == Kind::ABIEncodeWithSignature ||
m_kind == Kind::ABIDecode ||
m_kind == Kind::MetaType ||
Expand Down Expand Up @@ -4175,6 +4177,16 @@ MemberList::MemberMap MagicType::nativeMembers(ASTNode const*) const
nullptr,
FunctionType::Options::withArbitraryParameters()
)},
{"encodeError", TypeProvider::function(
TypePointers{},
TypePointers{TypeProvider::array(DataLocation::Memory)},
strings{},
strings{1, ""},
FunctionType::Kind::ABIEncodeError,
StateMutability::Pure,
nullptr,
FunctionType::Options::withArbitraryParameters()
)},
{"encodeWithSignature", TypeProvider::function(
TypePointers{TypeProvider::array(DataLocation::Memory, true)},
TypePointers{TypeProvider::array(DataLocation::Memory)},
Expand Down
1 change: 1 addition & 0 deletions libsolidity/ast/Types.h
Expand Up @@ -1284,6 +1284,7 @@ class FunctionType: public Type
ABIEncodePacked,
ABIEncodeWithSelector,
ABIEncodeCall,
ABIEncodeError,
ABIEncodeWithSignature,
ABIDecode,
GasLeft, ///< gasleft()
Expand Down
17 changes: 14 additions & 3 deletions libsolidity/codegen/ExpressionCompiler.cpp
Expand Up @@ -1298,20 +1298,25 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
case FunctionType::Kind::ABIEncodePacked:
case FunctionType::Kind::ABIEncodeWithSelector:
case FunctionType::Kind::ABIEncodeCall:
case FunctionType::Kind::ABIEncodeError:
case FunctionType::Kind::ABIEncodeWithSignature:
{
bool const isPacked = function.kind() == FunctionType::Kind::ABIEncodePacked;
bool const hasSelectorOrSignature =
function.kind() == FunctionType::Kind::ABIEncodeWithSelector ||
function.kind() == FunctionType::Kind::ABIEncodeCall ||
function.kind() == FunctionType::Kind::ABIEncodeError ||
function.kind() == FunctionType::Kind::ABIEncodeWithSignature;

TypePointers argumentTypes;
TypePointers targetTypes;

ASTNode::listAccept(arguments, *this);

if (function.kind() == FunctionType::Kind::ABIEncodeCall)
if (
function.kind() == FunctionType::Kind::ABIEncodeCall ||
function.kind() == FunctionType::Kind::ABIEncodeError
)
{
solAssert(arguments.size() == 2);

Expand Down Expand Up @@ -1392,10 +1397,16 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
dataOnStack = TypeProvider::fixedBytes(32);
}
}
else if (function.kind() == FunctionType::Kind::ABIEncodeCall)
else if (
function.kind() == FunctionType::Kind::ABIEncodeCall ||
function.kind() == FunctionType::Kind::ABIEncodeError
)
{
auto const& funType = dynamic_cast<FunctionType const&>(*selectorType);
if (funType.kind() == FunctionType::Kind::Declaration)
if (
funType.kind() == FunctionType::Kind::Declaration ||
funType.kind() == FunctionType::Kind::Error
)
{
solAssert(funType.hasDeclaration());
solAssert(selectorType->sizeOnStack() == 0);
Expand Down
19 changes: 15 additions & 4 deletions libsolidity/codegen/ir/IRGeneratorForStatements.cpp
Expand Up @@ -1169,13 +1169,15 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
case FunctionType::Kind::ABIEncodePacked:
case FunctionType::Kind::ABIEncodeWithSelector:
case FunctionType::Kind::ABIEncodeCall:
case FunctionType::Kind::ABIEncodeError:
case FunctionType::Kind::ABIEncodeWithSignature:
{
bool const isPacked = functionType->kind() == FunctionType::Kind::ABIEncodePacked;
solAssert(functionType->padArguments() != isPacked);
bool const hasSelectorOrSignature =
functionType->kind() == FunctionType::Kind::ABIEncodeWithSelector ||
functionType->kind() == FunctionType::Kind::ABIEncodeCall ||
functionType->kind() == FunctionType::Kind::ABIEncodeError ||
functionType->kind() == FunctionType::Kind::ABIEncodeWithSignature;

TypePointers argumentTypes;
Expand All @@ -1184,7 +1186,10 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
std::string selector;
std::vector<ASTPointer<Expression const>> argumentsOfEncodeFunction;

if (functionType->kind() == FunctionType::Kind::ABIEncodeCall)
if (
functionType->kind() == FunctionType::Kind::ABIEncodeCall ||
functionType->kind() == FunctionType::Kind::ABIEncodeError
)
{
solAssert(arguments.size() == 2);
// Account for tuples with one component which become that component
Expand Down Expand Up @@ -1212,7 +1217,10 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
argumentVars += IRVariable(*argument).stackSlots();
}

if (functionType->kind() == FunctionType::Kind::ABIEncodeCall)
if (
functionType->kind() == FunctionType::Kind::ABIEncodeCall ||
functionType->kind() == FunctionType::Kind::ABIEncodeError
)
{
auto encodedFunctionType = dynamic_cast<FunctionType const*>(arguments.front()->annotation().type);
solAssert(encodedFunctionType);
Expand All @@ -1225,7 +1233,10 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
targetTypes.emplace_back(type(*argument).fullEncodingType(false, true, isPacked));


if (functionType->kind() == FunctionType::Kind::ABIEncodeCall)
if (
functionType->kind() == FunctionType::Kind::ABIEncodeCall ||
functionType->kind() == FunctionType::Kind::ABIEncodeError
)
{
auto const& selectorType = dynamic_cast<FunctionType const&>(type(*arguments.front()));
if (selectorType.kind() == FunctionType::Kind::Declaration)
Expand Down Expand Up @@ -1970,7 +1981,7 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)

define(_memberAccess) << requestedValue << "\n";
}
else if (std::set<std::string>{"encode", "encodePacked", "encodeWithSelector", "encodeCall", "encodeWithSignature", "decode"}.count(member))
else if (std::set<std::string>{"encode", "encodePacked", "encodeWithSelector", "encodeCall", "encodeError", "encodeWithSignature", "decode"}.count(member))
{
// no-op
}
Expand Down
2 changes: 2 additions & 0 deletions libsolidity/formal/SMTEncoder.cpp
Expand Up @@ -659,6 +659,7 @@ void SMTEncoder::endVisit(FunctionCall const& _funCall)
case FunctionType::Kind::ABIEncodePacked:
case FunctionType::Kind::ABIEncodeWithSelector:
case FunctionType::Kind::ABIEncodeCall:
case FunctionType::Kind::ABIEncodeError:
case FunctionType::Kind::ABIEncodeWithSignature:
visitABIFunction(_funCall);
break;
Expand Down Expand Up @@ -3111,6 +3112,7 @@ std::set<FunctionCall const*, ASTCompareByID<FunctionCall>> SMTEncoder::collectA
case FunctionType::Kind::ABIEncodePacked:
case FunctionType::Kind::ABIEncodeWithSelector:
case FunctionType::Kind::ABIEncodeCall:
case FunctionType::Kind::ABIEncodeError:
case FunctionType::Kind::ABIEncodeWithSignature:
case FunctionType::Kind::ABIDecode:
abiCalls.insert(&_funCall);
Expand Down
@@ -0,0 +1,6 @@
error E(uint);

function f() pure {
abi.encodeError(E, (1));
}
// ----
@@ -0,0 +1,9 @@
library L {
event E(uint);
}

function f() {
abi.encodeError(L.E, (1));
}
// ----
// TypeError 3510: (69-72): Expected an error type. Cannot use events for abi.encodeError.
@@ -0,0 +1,6 @@
error g(uint);

function f() pure {
abi.encodeError(g, (1));
}
// ----
@@ -0,0 +1,7 @@
function g(uint) {}

function f() {
abi.encodeError(g, (1));
}
// ----
// TypeError 3510: (56-57): Expected an error type. Cannot use functions for abi.encodeError.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs another case for library functions.