Merge pull request #12287 from ethereum/abi.encodeCall

Implement typechecked abi.encodeCall()
This commit is contained in:
chriseth 2021-12-16 18:24:33 +01:00 committed by GitHub
commit 835efea427
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 538 additions and 26 deletions

View File

@ -1,6 +1,7 @@
### 0.8.11 (unreleased) ### 0.8.11 (unreleased)
Language Features: Language Features:
* General: New builtin function ``abi.encodeCall(functionPointer, (arg1, arg2, ...))`` that type-checks the arguments and returns the ABI-encoded function call data.
Compiler Features: Compiler Features:

View File

@ -80,6 +80,8 @@ Global Variables
the given arguments. Note that this encoding can be ambiguous! the given arguments. Note that this encoding can be ambiguous!
- ``abi.encodeWithSelector(bytes4 selector, ...) returns (bytes memory)``: :ref:`ABI <ABI>`-encodes - ``abi.encodeWithSelector(bytes4 selector, ...) returns (bytes memory)``: :ref:`ABI <ABI>`-encodes
the given arguments starting from the second and prepends the given four-byte selector the given arguments starting from the second and prepends the given four-byte selector
- ``abi.encodeCall(function functionPointer, (...)) returns (bytes memory)``: ABI-encodes a call to ``functionPointer`` with the arguments found in the
tuple. Performs a full type-check, ensuring the types match the function signature. Result equals ``abi.encodeWithSelector(functionPointer.selector, (...))``
- ``abi.encodeWithSignature(string memory signature, ...) returns (bytes memory)``: Equivalent - ``abi.encodeWithSignature(string memory signature, ...) returns (bytes memory)``: Equivalent
to ``abi.encodeWithSelector(bytes4(keccak256(bytes(signature)), ...)`` to ``abi.encodeWithSelector(bytes4(keccak256(bytes(signature)), ...)``
- ``bytes.concat(...) returns (bytes memory)``: :ref:`Concatenates variable number of - ``bytes.concat(...) returns (bytes memory)``: :ref:`Concatenates variable number of

View File

@ -136,6 +136,7 @@ ABI Encoding and Decoding Functions
- ``abi.encodePacked(...) returns (bytes memory)``: Performs :ref:`packed encoding <abi_packed_mode>` of the given arguments. Note that packed encoding can be ambiguous! - ``abi.encodePacked(...) returns (bytes memory)``: Performs :ref:`packed encoding <abi_packed_mode>` of the given arguments. Note that packed encoding can be ambiguous!
- ``abi.encodeWithSelector(bytes4 selector, ...) returns (bytes memory)``: ABI-encodes the given arguments starting from the second and prepends the given four-byte selector - ``abi.encodeWithSelector(bytes4 selector, ...) returns (bytes memory)``: ABI-encodes the given arguments starting from the second and prepends the given four-byte selector
- ``abi.encodeWithSignature(string memory signature, ...) returns (bytes memory)``: Equivalent to ``abi.encodeWithSelector(bytes4(keccak256(bytes(signature))), ...)`` - ``abi.encodeWithSignature(string memory signature, ...) returns (bytes memory)``: Equivalent to ``abi.encodeWithSelector(bytes4(keccak256(bytes(signature))), ...)``
- ``abi.encodeCall(function functionPointer, (...)) returns (bytes memory)``: ABI-encodes a call to ``functionPointer`` with the arguments found in the tuple. Performs a full type-check, ensuring the types match the function signature. Result equals ``abi.encodeWithSelector(functionPointer.selector, (...))``
.. note:: .. note::
These encoding functions can be used to craft data for external function calls without actually These encoding functions can be used to craft data for external function calls without actually

View File

@ -1996,6 +1996,7 @@ void TypeChecker::typeCheckABIEncodeFunctions(
_functionType->kind() == FunctionType::Kind::ABIEncode || _functionType->kind() == FunctionType::Kind::ABIEncode ||
_functionType->kind() == FunctionType::Kind::ABIEncodePacked || _functionType->kind() == FunctionType::Kind::ABIEncodePacked ||
_functionType->kind() == FunctionType::Kind::ABIEncodeWithSelector || _functionType->kind() == FunctionType::Kind::ABIEncodeWithSelector ||
_functionType->kind() == FunctionType::Kind::ABIEncodeCall ||
_functionType->kind() == FunctionType::Kind::ABIEncodeWithSignature, _functionType->kind() == FunctionType::Kind::ABIEncodeWithSignature,
"ABI function has unexpected FunctionType::Kind." "ABI function has unexpected FunctionType::Kind."
); );
@ -2020,6 +2021,13 @@ void TypeChecker::typeCheckABIEncodeFunctions(
// Perform standard function call type checking // Perform standard function call type checking
typeCheckFunctionGeneralChecks(_functionCall, _functionType); typeCheckFunctionGeneralChecks(_functionCall, _functionType);
// No further generic checks needed as we do a precise check for ABIEncodeCall
if (_functionType->kind() == FunctionType::Kind::ABIEncodeCall)
{
typeCheckABIEncodeCallFunction(_functionCall);
return;
}
// Check additional arguments for variadic functions // Check additional arguments for variadic functions
vector<ASTPointer<Expression const>> const& arguments = _functionCall.arguments(); vector<ASTPointer<Expression const>> const& arguments = _functionCall.arguments();
for (size_t i = 0; i < arguments.size(); ++i) for (size_t i = 0; i < arguments.size(); ++i)
@ -2078,6 +2086,110 @@ void TypeChecker::typeCheckABIEncodeFunctions(
} }
} }
void TypeChecker::typeCheckABIEncodeCallFunction(FunctionCall const& _functionCall)
{
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(
6219_error,
_functionCall.location(),
"Expected two arguments: a function pointer followed by a tuple."
);
return;
}
auto const functionPointerType = dynamic_cast<FunctionTypePointer>(type(*arguments.front()));
if (!functionPointerType)
{
m_errorReporter.typeError(
5511_error,
arguments.front()->location(),
"Expected first argument to be a function pointer, not \"" +
type(*arguments.front())->canonicalName() +
"\"."
);
return;
}
if (functionPointerType->kind() != FunctionType::Kind::External)
{
string msg = "Function must be \"public\" or \"external\".";
SecondarySourceLocation ssl{};
if (functionPointerType->hasDeclaration())
{
ssl.append("Function is declared here:", functionPointerType->declaration().location());
if (functionPointerType->declaration().scope() == m_currentContract)
msg += " Did you forget to prefix \"this.\"?";
}
m_errorReporter.typeError(3509_error, arguments[0]->location(), ssl, msg);
return;
}
solAssert(!functionPointerType->takesArbitraryParameters(), "Function must have fixed parameters.");
// Tuples with only one component become that component
vector<ASTPointer<Expression const>> callArguments;
auto const* tupleType = dynamic_cast<TupleType const*>(type(*arguments[1]));
if (tupleType)
{
auto const& argumentTuple = dynamic_cast<TupleExpression const&>(*arguments[1].get());
callArguments = decltype(callArguments){argumentTuple.components().begin(), argumentTuple.components().end()};
}
else
callArguments.push_back(arguments[1]);
if (functionPointerType->parameterTypes().size() != callArguments.size())
{
if (tupleType)
m_errorReporter.typeError(
7788_error,
_functionCall.location(),
"Expected " +
to_string(functionPointerType->parameterTypes().size()) +
" instead of " +
to_string(callArguments.size()) +
" components for the tuple parameter."
);
else
m_errorReporter.typeError(
7515_error,
_functionCall.location(),
"Expected a tuple with " +
to_string(functionPointerType->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 = min(callArguments.size(), functionPointerType->parameterTypes().size());
for (size_t i = 0; i < numParameters; i++)
{
Type const& argType = *type(*callArguments[i]);
BoolResult result = argType.isImplicitlyConvertibleTo(*functionPointerType->parameterTypes()[i]);
if (!result)
m_errorReporter.typeError(
5407_error,
callArguments[i]->location(),
"Cannot implicitly convert component at position " +
to_string(i) +
" from \"" +
argType.canonicalName() +
"\" to \"" +
functionPointerType->parameterTypes()[i]->canonicalName() +
"\"" +
(result.message().empty() ? "." : ": " + result.message())
);
}
}
void TypeChecker::typeCheckBytesConcatFunction( void TypeChecker::typeCheckBytesConcatFunction(
FunctionCall const& _functionCall, FunctionCall const& _functionCall,
FunctionType const* _functionType FunctionType const* _functionType
@ -2507,6 +2619,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
case FunctionType::Kind::ABIEncodePacked: case FunctionType::Kind::ABIEncodePacked:
case FunctionType::Kind::ABIEncodeWithSelector: case FunctionType::Kind::ABIEncodeWithSelector:
case FunctionType::Kind::ABIEncodeWithSignature: case FunctionType::Kind::ABIEncodeWithSignature:
case FunctionType::Kind::ABIEncodeCall:
{ {
typeCheckABIEncodeFunctions(_functionCall, functionType); typeCheckABIEncodeFunctions(_functionCall, functionType);
returnTypes = functionType->returnParameterTypes(); returnTypes = functionType->returnParameterTypes();

View File

@ -110,6 +110,9 @@ private:
FunctionTypePointer _functionType FunctionTypePointer _functionType
); );
/// Performs checks specific to the ABI encode functions of type ABIEncodeCall
void typeCheckABIEncodeCallFunction(FunctionCall const& _functionCall);
/// Performs general checks and checks specific to bytes concat function call /// Performs general checks and checks specific to bytes concat function call
void typeCheckBytesConcatFunction( void typeCheckBytesConcatFunction(
FunctionCall const& _functionCall, FunctionCall const& _functionCall,

View File

@ -367,6 +367,7 @@ void ViewPureChecker::endVisit(MemberAccess const& _memberAccess)
{MagicType::Kind::ABI, "encode"}, {MagicType::Kind::ABI, "encode"},
{MagicType::Kind::ABI, "encodePacked"}, {MagicType::Kind::ABI, "encodePacked"},
{MagicType::Kind::ABI, "encodeWithSelector"}, {MagicType::Kind::ABI, "encodeWithSelector"},
{MagicType::Kind::ABI, "encodeCall"},
{MagicType::Kind::ABI, "encodeWithSignature"}, {MagicType::Kind::ABI, "encodeWithSignature"},
{MagicType::Kind::Message, "data"}, {MagicType::Kind::Message, "data"},
{MagicType::Kind::Message, "sig"}, {MagicType::Kind::Message, "sig"},

View File

@ -2935,6 +2935,7 @@ string FunctionType::richIdentifier() const
case Kind::ABIEncode: id += "abiencode"; break; case Kind::ABIEncode: id += "abiencode"; break;
case Kind::ABIEncodePacked: id += "abiencodepacked"; break; case Kind::ABIEncodePacked: id += "abiencodepacked"; break;
case Kind::ABIEncodeWithSelector: id += "abiencodewithselector"; break; case Kind::ABIEncodeWithSelector: id += "abiencodewithselector"; break;
case Kind::ABIEncodeCall: id += "abiencodecall"; break;
case Kind::ABIEncodeWithSignature: id += "abiencodewithsignature"; break; case Kind::ABIEncodeWithSignature: id += "abiencodewithsignature"; break;
case Kind::ABIDecode: id += "abidecode"; break; case Kind::ABIDecode: id += "abidecode"; break;
case Kind::MetaType: id += "metatype"; break; case Kind::MetaType: id += "metatype"; break;
@ -3499,6 +3500,7 @@ bool FunctionType::isPure() const
m_kind == Kind::ABIEncode || m_kind == Kind::ABIEncode ||
m_kind == Kind::ABIEncodePacked || m_kind == Kind::ABIEncodePacked ||
m_kind == Kind::ABIEncodeWithSelector || m_kind == Kind::ABIEncodeWithSelector ||
m_kind == Kind::ABIEncodeCall ||
m_kind == Kind::ABIEncodeWithSignature || m_kind == Kind::ABIEncodeWithSignature ||
m_kind == Kind::ABIDecode || m_kind == Kind::ABIDecode ||
m_kind == Kind::MetaType || m_kind == Kind::MetaType ||
@ -4001,6 +4003,15 @@ MemberList::MemberMap MagicType::nativeMembers(ASTNode const*) const
true, true,
StateMutability::Pure StateMutability::Pure
)}, )},
{"encodeCall", TypeProvider::function(
TypePointers{},
TypePointers{TypeProvider::array(DataLocation::Memory)},
strings{},
strings{1, ""},
FunctionType::Kind::ABIEncodeCall,
true,
StateMutability::Pure
)},
{"encodeWithSignature", TypeProvider::function( {"encodeWithSignature", TypeProvider::function(
TypePointers{TypeProvider::array(DataLocation::Memory, true)}, TypePointers{TypeProvider::array(DataLocation::Memory, true)},
TypePointers{TypeProvider::array(DataLocation::Memory)}, TypePointers{TypeProvider::array(DataLocation::Memory)},

View File

@ -1237,6 +1237,7 @@ public:
ABIEncode, ABIEncode,
ABIEncodePacked, ABIEncodePacked,
ABIEncodeWithSelector, ABIEncodeWithSelector,
ABIEncodeCall,
ABIEncodeWithSignature, ABIEncodeWithSignature,
ABIDecode, ABIDecode,
GasLeft, ///< gasleft() GasLeft, ///< gasleft()

View File

@ -1236,28 +1236,47 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
case FunctionType::Kind::ABIEncode: case FunctionType::Kind::ABIEncode:
case FunctionType::Kind::ABIEncodePacked: case FunctionType::Kind::ABIEncodePacked:
case FunctionType::Kind::ABIEncodeWithSelector: case FunctionType::Kind::ABIEncodeWithSelector:
case FunctionType::Kind::ABIEncodeCall:
case FunctionType::Kind::ABIEncodeWithSignature: case FunctionType::Kind::ABIEncodeWithSignature:
{ {
bool const isPacked = function.kind() == FunctionType::Kind::ABIEncodePacked; bool const isPacked = function.kind() == FunctionType::Kind::ABIEncodePacked;
bool const hasSelectorOrSignature = bool const hasSelectorOrSignature =
function.kind() == FunctionType::Kind::ABIEncodeWithSelector || function.kind() == FunctionType::Kind::ABIEncodeWithSelector ||
function.kind() == FunctionType::Kind::ABIEncodeCall ||
function.kind() == FunctionType::Kind::ABIEncodeWithSignature; function.kind() == FunctionType::Kind::ABIEncodeWithSignature;
TypePointers argumentTypes; TypePointers argumentTypes;
TypePointers targetTypes;
for (unsigned i = 0; i < arguments.size(); ++i) ASTNode::listAccept(arguments, *this);
if (function.kind() == FunctionType::Kind::ABIEncodeCall)
{ {
arguments[i]->accept(*this); solAssert(arguments.size() == 2);
// Do not keep the selector as part of the ABI encoded args
if (!hasSelectorOrSignature || i > 0) auto const functionPtr = dynamic_cast<FunctionTypePointer>(arguments[0]->annotation().type);
argumentTypes.push_back(arguments[i]->annotation().type); solAssert(functionPtr);
solAssert(functionPtr->sizeOnStack() == 2);
// Account for tuples with one component which become that component
if (auto const tupleType = dynamic_cast<TupleType const*>(arguments[1]->annotation().type))
argumentTypes = tupleType->components();
else
argumentTypes.emplace_back(arguments[1]->annotation().type);
} }
else
for (unsigned i = 0; i < arguments.size(); ++i)
{
// Do not keep the selector as part of the ABI encoded args
if (!hasSelectorOrSignature || i > 0)
argumentTypes.push_back(arguments[i]->annotation().type);
}
utils().fetchFreeMemoryPointer(); utils().fetchFreeMemoryPointer();
// stack now: [<selector>] <arg1> .. <argN> <free_mem> // stack now: [<selector/functionPointer/signature>] <arg1> .. <argN> <free_mem>
// adjust by 32(+4) bytes to accommodate the length(+selector) // adjust by 32(+4) bytes to accommodate the length(+selector)
m_context << u256(32 + (hasSelectorOrSignature ? 4 : 0)) << Instruction::ADD; m_context << u256(32 + (hasSelectorOrSignature ? 4 : 0)) << Instruction::ADD;
// stack now: [<selector>] <arg1> .. <argN> <data_encoding_area_start> // stack now: [<selector/functionPointer/signature>] <arg1> .. <argN> <data_encoding_area_start>
if (isPacked) if (isPacked)
{ {
@ -1270,7 +1289,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
utils().abiEncode(argumentTypes, TypePointers()); utils().abiEncode(argumentTypes, TypePointers());
} }
utils().fetchFreeMemoryPointer(); utils().fetchFreeMemoryPointer();
// stack: [<selector>] <data_encoding_area_end> <bytes_memory_ptr> // stack: [<selector/functionPointer/signature>] <data_encoding_area_end> <bytes_memory_ptr>
// size is end minus start minus length slot // size is end minus start minus length slot
m_context.appendInlineAssembly(R"({ m_context.appendInlineAssembly(R"({
@ -1278,16 +1297,17 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
})", {"mem_end", "mem_ptr"}); })", {"mem_end", "mem_ptr"});
m_context << Instruction::SWAP1; m_context << Instruction::SWAP1;
utils().storeFreeMemoryPointer(); utils().storeFreeMemoryPointer();
// stack: [<selector>] <memory ptr> // stack: [<selector/functionPointer/signature>] <memory ptr>
if (hasSelectorOrSignature) if (hasSelectorOrSignature)
{ {
// stack: <selector> <memory pointer> // stack: <selector/functionPointer/signature> <memory pointer>
solAssert(arguments.size() >= 1, ""); solAssert(arguments.size() >= 1, "");
Type const* selectorType = arguments[0]->annotation().type; Type const* selectorType = arguments[0]->annotation().type;
utils().moveIntoStack(selectorType->sizeOnStack()); utils().moveIntoStack(selectorType->sizeOnStack());
Type const* dataOnStack = selectorType; Type const* dataOnStack = selectorType;
// stack: <memory pointer> <selector>
// stack: <memory pointer> <selector/functionPointer/signature>
if (function.kind() == FunctionType::Kind::ABIEncodeWithSignature) if (function.kind() == FunctionType::Kind::ABIEncodeWithSignature)
{ {
// hash the signature // hash the signature
@ -1299,7 +1319,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
else else
{ {
utils().fetchFreeMemoryPointer(); utils().fetchFreeMemoryPointer();
// stack: <memory pointer> <selector> <free mem ptr> // stack: <memory pointer> <signature> <free mem ptr>
utils().packedEncode(TypePointers{selectorType}, TypePointers()); utils().packedEncode(TypePointers{selectorType}, TypePointers());
utils().toSizeAfterFreeMemoryPointer(); utils().toSizeAfterFreeMemoryPointer();
m_context << Instruction::KECCAK256; m_context << Instruction::KECCAK256;
@ -1308,10 +1328,16 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
dataOnStack = TypeProvider::fixedBytes(32); dataOnStack = TypeProvider::fixedBytes(32);
} }
} }
else else if (function.kind() == FunctionType::Kind::ABIEncodeCall)
{ {
solAssert(function.kind() == FunctionType::Kind::ABIEncodeWithSelector, ""); // stack: <memory pointer> <functionPointer>
// Extract selector from the stack
m_context << Instruction::SWAP1 << Instruction::POP;
// Conversion will be done below
dataOnStack = TypeProvider::uint(32);
} }
else
solAssert(function.kind() == FunctionType::Kind::ABIEncodeWithSelector, "");
utils().convertType(*dataOnStack, FixedBytesType(4), true); utils().convertType(*dataOnStack, FixedBytesType(4), true);

View File

@ -1104,29 +1104,57 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
case FunctionType::Kind::ABIEncode: case FunctionType::Kind::ABIEncode:
case FunctionType::Kind::ABIEncodePacked: case FunctionType::Kind::ABIEncodePacked:
case FunctionType::Kind::ABIEncodeWithSelector: case FunctionType::Kind::ABIEncodeWithSelector:
case FunctionType::Kind::ABIEncodeCall:
case FunctionType::Kind::ABIEncodeWithSignature: case FunctionType::Kind::ABIEncodeWithSignature:
{ {
bool const isPacked = functionType->kind() == FunctionType::Kind::ABIEncodePacked; bool const isPacked = functionType->kind() == FunctionType::Kind::ABIEncodePacked;
solAssert(functionType->padArguments() != isPacked, ""); solAssert(functionType->padArguments() != isPacked, "");
bool const hasSelectorOrSignature = bool const hasSelectorOrSignature =
functionType->kind() == FunctionType::Kind::ABIEncodeWithSelector || functionType->kind() == FunctionType::Kind::ABIEncodeWithSelector ||
functionType->kind() == FunctionType::Kind::ABIEncodeCall ||
functionType->kind() == FunctionType::Kind::ABIEncodeWithSignature; functionType->kind() == FunctionType::Kind::ABIEncodeWithSignature;
TypePointers argumentTypes; TypePointers argumentTypes;
TypePointers targetTypes; TypePointers targetTypes;
vector<string> argumentVars; vector<string> argumentVars;
for (size_t i = 0; i < arguments.size(); ++i) string selector;
vector<ASTPointer<Expression const>> argumentsOfEncodeFunction;
if (functionType->kind() == FunctionType::Kind::ABIEncodeCall)
{ {
// ignore selector solAssert(arguments.size() == 2, "");
if (hasSelectorOrSignature && i == 0) // Account for tuples with one component which become that component
continue; if (type(*arguments[1]).category() == Type::Category::Tuple)
argumentTypes.emplace_back(&type(*arguments[i])); {
targetTypes.emplace_back(type(*arguments[i]).fullEncodingType(false, true, isPacked)); auto const& tupleExpression = dynamic_cast<TupleExpression const&>(*arguments[1]);
argumentVars += IRVariable(*arguments[i]).stackSlots(); for (auto component: tupleExpression.components())
argumentsOfEncodeFunction.push_back(component);
}
else
argumentsOfEncodeFunction.push_back(arguments[1]);
}
else
for (size_t i = 0; i < arguments.size(); ++i)
{
// ignore selector
if (hasSelectorOrSignature && i == 0)
continue;
argumentsOfEncodeFunction.push_back(arguments[i]);
}
for (auto const& argument: argumentsOfEncodeFunction)
{
argumentTypes.emplace_back(&type(*argument));
targetTypes.emplace_back(type(*argument).fullEncodingType(false, true, isPacked));
argumentVars += IRVariable(*argument).stackSlots();
} }
string selector; if (functionType->kind() == FunctionType::Kind::ABIEncodeCall)
if (functionType->kind() == FunctionType::Kind::ABIEncodeWithSignature) selector = convert(
IRVariable(*arguments[0]).part("functionSelector"),
*TypeProvider::fixedBytes(4)
).name();
else if (functionType->kind() == FunctionType::Kind::ABIEncodeWithSignature)
{ {
// hash the signature // hash the signature
Type const& selectorType = type(*arguments.front()); Type const& selectorType = type(*arguments.front());
@ -1833,7 +1861,7 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
define(_memberAccess) << requestedValue << "\n"; define(_memberAccess) << requestedValue << "\n";
} }
else if (set<string>{"encode", "encodePacked", "encodeWithSelector", "encodeWithSignature", "decode"}.count(member)) else if (set<string>{"encode", "encodePacked", "encodeWithSelector", "encodeCall", "encodeWithSignature", "decode"}.count(member))
{ {
// no-op // no-op
} }

View File

@ -637,6 +637,7 @@ void SMTEncoder::endVisit(FunctionCall const& _funCall)
case FunctionType::Kind::ABIEncode: case FunctionType::Kind::ABIEncode:
case FunctionType::Kind::ABIEncodePacked: case FunctionType::Kind::ABIEncodePacked:
case FunctionType::Kind::ABIEncodeWithSelector: case FunctionType::Kind::ABIEncodeWithSelector:
case FunctionType::Kind::ABIEncodeCall:
case FunctionType::Kind::ABIEncodeWithSignature: case FunctionType::Kind::ABIEncodeWithSignature:
visitABIFunction(_funCall); visitABIFunction(_funCall);
break; break;
@ -3041,6 +3042,7 @@ set<FunctionCall const*> SMTEncoder::collectABICalls(ASTNode const* _node)
case FunctionType::Kind::ABIEncode: case FunctionType::Kind::ABIEncode:
case FunctionType::Kind::ABIEncodePacked: case FunctionType::Kind::ABIEncodePacked:
case FunctionType::Kind::ABIEncodeWithSelector: case FunctionType::Kind::ABIEncodeWithSelector:
case FunctionType::Kind::ABIEncodeCall:
case FunctionType::Kind::ABIEncodeWithSignature: case FunctionType::Kind::ABIEncodeWithSignature:
case FunctionType::Kind::ABIDecode: case FunctionType::Kind::ABIDecode:
abiCalls.insert(&_funCall); abiCalls.insert(&_funCall);

View File

@ -236,6 +236,15 @@ void SymbolicState::buildABIFunctions(set<FunctionCall const*> const& _abiFuncti
else else
solAssert(false, "Unexpected argument of abi.decode"); solAssert(false, "Unexpected argument of abi.decode");
} }
else if (t->kind() == FunctionType::Kind::ABIEncodeCall)
{
// abi.encodeCall : (functionPointer, tuple_of_args_or_one_non_tuple_arg(arguments)) -> bytes
solAssert(args.size() == 2, "Unexpected number of arguments for abi.encodeCall");
outTypes.emplace_back(TypeProvider::bytesMemory());
inTypes.emplace_back(args.at(0)->annotation().type);
inTypes.emplace_back(args.at(1)->annotation().type);
}
else else
{ {
outTypes = returnTypes; outTypes = returnTypes;

View File

@ -0,0 +1,51 @@
pragma abicoder v2;
contract C {
type UnsignedNumber is uint256;
enum Enum { First, Second, Third }
struct Struct {
UnsignedNumber[] dynamicArray;
uint256 justAnInt;
string name;
bytes someBytes;
Enum theEnum;
}
function callMeMaybe(Struct calldata _data, int256 _intVal, string memory _nameVal) external pure {
assert(_data.dynamicArray.length == 3);
assert(UnsignedNumber.unwrap(_data.dynamicArray[0]) == 0);
assert(UnsignedNumber.unwrap(_data.dynamicArray[1]) == 1);
assert(UnsignedNumber.unwrap(_data.dynamicArray[2]) == 2);
assert(_data.justAnInt == 6);
assert(keccak256(bytes(_data.name)) == keccak256("StructName"));
assert(keccak256(_data.someBytes) == keccak256(bytes("1234")));
assert(_data.theEnum == Enum.Second);
assert(_intVal == 5);
assert(keccak256(bytes(_nameVal)) == keccak256("TestName"));
}
function callExternal() public returns (bool) {
Struct memory structToSend;
structToSend.dynamicArray = new UnsignedNumber[](3);
structToSend.dynamicArray[0] = UnsignedNumber.wrap(0);
structToSend.dynamicArray[1] = UnsignedNumber.wrap(1);
structToSend.dynamicArray[2] = UnsignedNumber.wrap(2);
structToSend.justAnInt = 6;
structToSend.name = "StructName";
structToSend.someBytes = bytes("1234");
structToSend.theEnum = Enum.Second;
(bool success,) = address(this).call(abi.encodeCall(this.callMeMaybe, (
structToSend,
5,
"TestName"
)));
return success;
}
}
// ====
// compileViaYul: also
// ----
// callExternal() -> true

View File

@ -0,0 +1,63 @@
pragma abicoder v2;
contract C {
bool sideEffectRan = false;
function(uint256, string memory) external fPointer;
function fExternal(uint256 p, string memory t) external {}
string xstor;
function getExternalFunctionPointer() public returns (function(uint256, string memory) external) {
sideEffectRan = true;
return this.fExternal;
}
function fSignatureFromLiteral() public pure returns (bytes memory) {
return abi.encodeWithSignature("fExternal(uint256,string)", 1, "123");
}
function fSignatureFromLiteralCall() public view returns (bytes memory) {
return abi.encodeCall(this.fExternal, (1, "123"));
}
function fSignatureFromMemory() public pure returns (bytes memory) {
string memory x = "fExternal(uint256,string)";
return abi.encodeWithSignature(x, 1, "123");
}
function fSignatureFromMemoryCall() public view returns (bytes memory) {
return abi.encodeCall(this.fExternal, (1,"123"));
}
function fSignatureFromMemorys() public returns (bytes memory) {
xstor = "fExternal(uint256,string)";
return abi.encodeWithSignature(xstor, 1, "123");
}
function fPointerCall() public returns(bytes memory) {
fPointer = this.fExternal;
return abi.encodeCall(fPointer, (1, "123"));
}
function fLocalPointerCall() public returns(bytes memory) {
function(uint256, string memory) external localFunctionPointer = this.fExternal;
return abi.encodeCall(localFunctionPointer, (1, "123"));
}
function fReturnedFunctionPointer() public returns (bytes memory) {
return abi.encodeCall(getExternalFunctionPointer(), (1, "123"));
}
function assertConsistentSelectors() public {
assert(keccak256(fSignatureFromLiteral()) == keccak256(fSignatureFromLiteralCall()));
assert(keccak256(fSignatureFromMemory()) == keccak256(fSignatureFromMemoryCall()));
assert(keccak256(fSignatureFromMemoryCall()) == keccak256(fSignatureFromMemorys()));
assert(keccak256(fPointerCall()) == keccak256(fSignatureFromLiteral()));
assert(keccak256(fLocalPointerCall()) == keccak256(fSignatureFromLiteral()));
assert(keccak256(fReturnedFunctionPointer()) == keccak256(fSignatureFromLiteral()));
}
}
// ====
// compileViaYul: also
// ----
// assertConsistentSelectors() ->
// fSignatureFromLiteral() -> 0x20, 0x84, 23450202028776381066253055403048136312616272755117076566855971503345107992576, 26959946667150639794667015087019630673637144422540572481103610249216, 1725436586697640946858688965569256363112777243042596638790631055949824, 86060793054017993816230018372407419485142305772921726565498526629888, 0
// fSignatureFromLiteralCall() -> 0x20, 0x84, 23450202028776381066253055403048136312616272755117076566855971503345107992576, 26959946667150639794667015087019630673637144422540572481103610249216, 1725436586697640946858688965569256363112777243042596638790631055949824, 86060793054017993816230018372407419485142305772921726565498526629888, 0
// fSignatureFromMemory() -> 0x20, 0x84, 23450202028776381066253055403048136312616272755117076566855971503345107992576, 26959946667150639794667015087019630673637144422540572481103610249216, 1725436586697640946858688965569256363112777243042596638790631055949824, 86060793054017993816230018372407419485142305772921726565498526629888, 0
// fSignatureFromMemoryCall() -> 0x20, 0x84, 23450202028776381066253055403048136312616272755117076566855971503345107992576, 26959946667150639794667015087019630673637144422540572481103610249216, 1725436586697640946858688965569256363112777243042596638790631055949824, 86060793054017993816230018372407419485142305772921726565498526629888, 0
// fSignatureFromMemorys() -> 0x20, 0x84, 23450202028776381066253055403048136312616272755117076566855971503345107992576, 26959946667150639794667015087019630673637144422540572481103610249216, 1725436586697640946858688965569256363112777243042596638790631055949824, 86060793054017993816230018372407419485142305772921726565498526629888, 0
// fPointerCall() -> 0x20, 0x84, 23450202028776381066253055403048136312616272755117076566855971503345107992576, 26959946667150639794667015087019630673637144422540572481103610249216, 1725436586697640946858688965569256363112777243042596638790631055949824, 86060793054017993816230018372407419485142305772921726565498526629888, 0
// fLocalPointerCall() -> 0x20, 0x84, 23450202028776381066253055403048136312616272755117076566855971503345107992576, 26959946667150639794667015087019630673637144422540572481103610249216, 1725436586697640946858688965569256363112777243042596638790631055949824, 86060793054017993816230018372407419485142305772921726565498526629888, 0
// fReturnedFunctionPointer() -> 0x20, 0x84, 23450202028776381066253055403048136312616272755117076566855971503345107992576, 26959946667150639794667015087019630673637144422540572481103610249216, 1725436586697640946858688965569256363112777243042596638790631055949824, 86060793054017993816230018372407419485142305772921726565498526629888, 0

View File

@ -0,0 +1,28 @@
pragma abicoder v2;
contract D {
function something() external pure {}
}
contract C {
function something() external pure {}
function test() external returns (bytes4) {
function() external[2] memory x;
x[0] = this.something;
x[1] = (new D()).something;
function() external f = x[1];
bytes memory a = abi.encodeCall(x[0], ());
bytes memory b = abi.encodeCall(x[1], ());
bytes memory c = abi.encodeCall(f, ());
assert(a.length == 4 && b.length == 4 && c.length == 4);
assert(bytes4(a) == bytes4(b));
assert(bytes4(a) == bytes4(c));
assert(bytes4(a) == f.selector);
return bytes4(a);
}
}
// ====
// compileViaYul: also
// ----
// test() -> 0xa7a0d53700000000000000000000000000000000000000000000000000000000

View File

@ -0,0 +1,48 @@
pragma abicoder v2;
contract C {
bool sideEffectRan = false;
function fNoArgs() external {}
function fArray(uint[] memory x) external {}
function fUint(uint x, uint y) external returns (uint a, uint b) {}
function fSignatureFromLiteralNoArgs() public pure returns (bytes memory) {
return abi.encodeWithSignature("fNoArgs()");
}
function fPointerNoArgs() public view returns (bytes memory) {
return abi.encodeCall(this.fNoArgs, ());
}
function fSignatureFromLiteralArray() public pure returns (bytes memory) {
uint[] memory x;
return abi.encodeWithSignature("fArray(uint256[])", x);
}
function fPointerArray() public view returns (bytes memory) {
uint[] memory x;
return abi.encodeCall(this.fArray, x);
}
function fSignatureFromLiteralUint() public pure returns (bytes memory) {
return abi.encodeWithSignature("fUint(uint256,uint256)", 12, 13);
}
function fPointerUint() public view returns (bytes memory) {
return abi.encodeCall(this.fUint, (12,13));
}
function assertConsistentSelectors() public view {
assert(keccak256(fSignatureFromLiteralNoArgs()) == keccak256(fPointerNoArgs()));
assert(keccak256(fSignatureFromLiteralArray()) == keccak256(fPointerArray()));
assert(keccak256(fSignatureFromLiteralUint()) == keccak256(fPointerUint()));
}
}
// ====
// compileViaYul: also
// ----
// assertConsistentSelectors() ->
// fSignatureFromLiteralNoArgs() -> 0x20, 0x04, 12200448252684243758085936796735499259670113115893304444050964496075123064832
// fPointerNoArgs() -> 0x20, 4, 12200448252684243758085936796735499259670113115893304444050964496075123064832
// fSignatureFromLiteralArray() -> 0x20, 0x44, 4612216551196396486909126966576324289294165774260092952932219511233230929920, 862718293348820473429344482784628181556388621521298319395315527974912, 0
// fPointerArray() -> 0x20, 0x44, 4612216551196396486909126966576324289294165774260092952932219511233230929920, 862718293348820473429344482784628181556388621521298319395315527974912, 0
// fPointerUint() -> 0x20, 0x44, 30372892641494467502622535050667754357470287521126424526399600764424271429632, 323519360005807677536004181044235568083645733070486869773243322990592, 350479306672958317330671196131255198757282877493027442254346933239808
// fSignatureFromLiteralUint() -> 0x20, 0x44, 30372892641494467502622535050667754357470287521126424526399600764424271429632, 323519360005807677536004181044235568083645733070486869773243322990592, 350479306672958317330671196131255198757282877493027442254346933239808

View File

@ -26,7 +26,6 @@ contract C {
} }
struct S { uint a; string b; uint16 c; } struct S { uint a; string b; uint16 c; }
function f4() public pure returns (bytes memory) { function f4() public pure returns (bytes memory) {
bytes4 x = 0x12345678;
S memory s; S memory s;
s.a = 0x1234567; s.a = 0x1234567;
s.b = "Lorem ipsum dolor sit ethereum........"; s.b = "Lorem ipsum dolor sit ethereum........";

View File

@ -0,0 +1,26 @@
contract C {
function callMeMaybe(uint a, uint b, uint[] memory c) external {}
function abiEncodeSimple(uint x, uint y, uint z, uint[] memory a, uint[] memory b) public view {
require(x == y);
bytes memory b1 = abi.encodeCall(this.callMeMaybe, (x, z, a));
bytes memory b2 = abi.encodeCall(this.callMeMaybe, (y, z, a));
assert(b1.length == b2.length);
bytes memory b3 = abi.encodeCall(this.callMeMaybe, (y, z, b));
assert(b1.length == b3.length); // should fail
}
}
// ====
// SMTEngine: all
// SMTIgnoreCex: yes
// ----
// Warning 6031: (233-249): Internal error: Expression undefined for SMT solver.
// Warning 6031: (298-314): Internal error: Expression undefined for SMT solver.
// Warning 6031: (398-414): Internal error: Expression undefined for SMT solver.
// Warning 1218: (330-360): CHC: Error trying to invoke SMT solver.
// Warning 1218: (430-460): CHC: Error trying to invoke SMT solver.
// Warning 6328: (330-360): CHC: Assertion violation might happen here.
// Warning 6328: (430-460): CHC: Assertion violation might happen here.
// Warning 4661: (330-360): BMC: Assertion violation happens here.
// Warning 4661: (430-460): BMC: Assertion violation happens here.

View File

@ -0,0 +1,82 @@
interface I {
function fExternal(uint256 p, string memory t) external;
}
library L {
function fExternal(uint256 p, string memory t) external {}
}
contract C {
using L for uint256;
function f(int a) public {}
function f2(int a, string memory b) public {}
function f3(int a, int b) public {}
function f4() public {}
function fInternal(uint256 p, string memory t) internal {}
function failFunctionArgsWrongType() public returns(bytes memory) {
return abi.encodeCall(this.f, ("test"));
}
function failFunctionArgsTooMany() public returns(bytes memory) {
return abi.encodeCall(this.f, (1, 2));
}
function failFunctionArgsTooFew0() public returns(bytes memory) {
return abi.encodeCall(this.f, ());
}
function failFunctionArgsTooFew1() public returns(bytes memory) {
return abi.encodeCall(this.f);
}
function failFunctionPtrMissing() public returns(bytes memory) {
return abi.encodeCall(1, this.f);
}
function failFunctionPtrWrongType() public returns(bytes memory) {
return abi.encodeCall(abi.encodeCall, (1, 2, 3, "test"));
}
function failFunctionInternal() public returns(bytes memory) {
return abi.encodeCall(fInternal, (1, "123"));
}
function failFunctionInternalFromVariable() public returns(bytes memory) {
function(uint256, string memory) internal localFunctionPointer = fInternal;
return abi.encodeCall(localFunctionPointer, (1, "123"));
}
function failFunctionArgsArrayLiteral() public returns(bytes memory) {
return abi.encodeCall(this.f3, [1, 2]);
}
function failLibraryPointerCall() public returns (bytes memory) {
return abi.encodeCall(L.fExternal, (1, "123"));
}
function failBoundLibraryPointerCall() public returns (bytes memory) {
uint256 x = 1;
return abi.encodeCall(x.fExternal, (1, "123"));
}
function failInterfacePointerCall() public returns (bytes memory) {
return abi.encodeCall(I.fExternal, (1, "123"));
}
function successFunctionArgsIntLiteralTuple() public returns(bytes memory) {
return abi.encodeCall(this.f, (1));
}
function successFunctionArgsIntLiteral() public returns(bytes memory) {
return abi.encodeCall(this.f, 1);
}
function successFunctionArgsLiteralTuple() public returns(bytes memory) {
return abi.encodeCall(this.f2, (1, "test"));
}
function successFunctionArgsEmptyTuple() public returns(bytes memory) {
return abi.encodeCall(this.f4, ());
}
}
// ----
// TypeError 5407: (486-494): Cannot implicitly convert component at position 0 from "literal_string "test"" to "int256".
// TypeError 7788: (576-606): Expected 1 instead of 2 components for the tuple parameter.
// TypeError 7788: (687-713): Expected 1 instead of 0 components for the tuple parameter.
// TypeError 6219: (794-816): Expected two arguments: a function pointer followed by a tuple.
// TypeError 5511: (911-912): Expected first argument to be a function pointer, not "int_const 1".
// TypeError 3509: (1018-1032): Function must be "public" or "external".
// TypeError 3509: (1145-1154): Function must be "public" or "external". Did you forget to prefix "this."?
// TypeError 3509: (1350-1370): Function must be "public" or "external".
// TypeError 7515: (1469-1500): Expected a tuple with 2 components instead of a single non-tuple parameter.
// TypeError 5407: (1493-1499): Cannot implicitly convert component at position 0 from "uint8[2]" to "int256".
// TypeError 3509: (1596-1607): Function must be "public" or "external".
// TypeError 3509: (1738-1749): Function must be "public" or "external".
// TypeError 3509: (1860-1871): Function must be "public" or "external".

View File

@ -0,0 +1,9 @@
contract C {
function f(int a, int b) public {}
function failFunctionArgsIntLiteralNestedTuple() public returns(bytes memory) {
return abi.encodeCall(this.f, ((1,2)));
}
}
// ----
// TypeError 7788: (139-170): Expected 2 instead of 1 components for the tuple parameter.
// TypeError 5407: (163-168): Cannot implicitly convert component at position 0 from "tuple(int_const 1,int_const 2)" to "int256".

View File

@ -0,0 +1,8 @@
contract C {
function f(int a) public {}
function failFunctionArgsIntLiteralTuple() public returns(bytes memory) {
return abi.encodeCall(this.f, (1,));
}
}
// ----
// TypeError 8381: (149-153): Tuple component cannot be empty.