diff --git a/Changelog.md b/Changelog.md index e4022b2b1..b88e1a971 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,9 @@ ### 0.8.12 (unreleased) +Language Features: + * General: Support ``ContractName.functionName`` for ``abi.encodeCall``, in addition to external function pointers. + + Compiler Features: diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index d6c589f75..d4cffc00e 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -2122,16 +2122,35 @@ void TypeChecker::typeCheckABIEncodeCallFunction(FunctionCall const& _functionCa return; } - if (functionPointerType->kind() != FunctionType::Kind::External) + if ( + functionPointerType->kind() != FunctionType::Kind::External && + functionPointerType->kind() != FunctionType::Kind::Declaration + ) { - string msg = "Function must be \"public\" or \"external\"."; + string msg = "Expected regular external function type, or external view on public function."; + if (functionPointerType->kind() == FunctionType::Kind::Internal) + msg += " Provided internal function."; + else if (functionPointerType->kind() == FunctionType::Kind::DelegateCall) + msg += " Cannot use library functions for abi.encodeCall."; + else if (functionPointerType->kind() == FunctionType::Kind::Creation) + msg += " Provided creation function."; + else + msg += " Cannot use special function."; SecondarySourceLocation ssl{}; if (functionPointerType->hasDeclaration()) { ssl.append("Function is declared here:", functionPointerType->declaration().location()); - if (functionPointerType->declaration().scope() == m_currentContract) + if ( + functionPointerType->declaration().visibility() == Visibility::Public && + functionPointerType->declaration().scope() == m_currentContract + ) msg += " Did you forget to prefix \"this.\"?"; + else if (contains( + m_currentContract->annotation().linearizedBaseContracts, + functionPointerType->declaration().scope() + ) && functionPointerType->declaration().scope() != m_currentContract) + msg += " Functions from base contracts have to be external."; } m_errorReporter.typeError(3509_error, arguments[0]->location(), ssl, msg); diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 5a633d2e6..78b6f02b6 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -1255,7 +1255,6 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) auto const functionPtr = dynamic_cast(arguments[0]->annotation().type); solAssert(functionPtr); - solAssert(functionPtr->sizeOnStack() == 2); // Account for tuples with one component which become that component if (auto const tupleType = dynamic_cast(arguments[1]->annotation().type)) @@ -1330,9 +1329,20 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) } else if (function.kind() == FunctionType::Kind::ABIEncodeCall) { - // stack: - // Extract selector from the stack - m_context << Instruction::SWAP1 << Instruction::POP; + auto const& funType = dynamic_cast(*selectorType); + if (funType.kind() == FunctionType::Kind::Declaration) + { + solAssert(funType.hasDeclaration()); + solAssert(selectorType->sizeOnStack() == 0); + m_context << funType.externalIdentifier(); + } + else + { + solAssert(selectorType->sizeOnStack() == 2); + // stack: + // Extract selector from the stack + m_context << Instruction::SWAP1 << Instruction::POP; + } // Conversion will be done below dataOnStack = TypeProvider::uint(32); } diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index 179b3e739..d35685037 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -1150,10 +1150,21 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) } if (functionType->kind() == FunctionType::Kind::ABIEncodeCall) - selector = convert( - IRVariable(*arguments[0]).part("functionSelector"), - *TypeProvider::fixedBytes(4) - ).name(); + { + auto const& selectorType = dynamic_cast(type(*arguments.front())); + if (selectorType.kind() == FunctionType::Kind::Declaration) + { + solAssert(selectorType.hasDeclaration()); + selector = formatNumber(selectorType.externalIdentifier() << (256 - 32)); + } + else + { + selector = convert( + IRVariable(*arguments[0]).part("functionSelector"), + *TypeProvider::fixedBytes(4) + ).name(); + } + } else if (functionType->kind() == FunctionType::Kind::ABIEncodeWithSignature) { // hash the signature diff --git a/test/libsolidity/semanticTests/abiencodedecode/abi_encode_call_declaration.sol b/test/libsolidity/semanticTests/abiencodedecode/abi_encode_call_declaration.sol new file mode 100644 index 000000000..a70c6b903 --- /dev/null +++ b/test/libsolidity/semanticTests/abiencodedecode/abi_encode_call_declaration.sol @@ -0,0 +1,54 @@ +pragma abicoder v2; + +contract X { + // no "returns" on purpose + function a(uint) public pure {} + function b(uint) external pure {} +} + +contract Base { + function a(uint x) external pure returns (uint) { return x + 1; } +} + +contract C is Base { + function test() public view returns (uint r) { + bool success; + bytes memory result; + (success, result) = address(this).staticcall(abi.encodeCall(X.a, 1)); + require(success && result.length == 32); + r += abi.decode(result, (uint)); + require(r == 2); + + (success, result) = address(this).staticcall(abi.encodeCall(X.b, 10)); + require(success && result.length == 32); + r += abi.decode(result, (uint)); + require(r == 13); + + (success, result) = address(this).staticcall(abi.encodeCall(Base.a, 100)); + require(success && result.length == 32); + r += abi.decode(result, (uint)); + require(r == 114); + + (success, result) = address(this).staticcall(abi.encodeCall(this.a, 1000)); + require(success && result.length == 32); + r += abi.decode(result, (uint)); + require(r == 1115); + + (success, result) = address(this).staticcall(abi.encodeCall(C.b, 10000)); + require(success && result.length == 32); + r += abi.decode(result, (uint)); + require(r == 11116); + + return r; + } + function b(uint x) external view returns (uint) { + return this.a(x); + } + +} + +// ==== +// compileViaYul: also +// EVMVersion: >=byzantium +// ---- +// test() -> 11116 diff --git a/test/libsolidity/syntaxTests/specialFunctions/encodeCall.sol b/test/libsolidity/syntaxTests/specialFunctions/encodeCall.sol index 41b8f16cc..6cf39cfae 100644 --- a/test/libsolidity/syntaxTests/specialFunctions/encodeCall.sol +++ b/test/libsolidity/syntaxTests/specialFunctions/encodeCall.sol @@ -2,81 +2,47 @@ interface I { function fExternal(uint256 p, string memory t) external; } -library L { - function fExternal(uint256 p, string memory t) external {} +contract Other { + function fExternal(uint) external pure {} + function fPublic(uint) public pure {} + function fInternal(uint) internal pure {} } -contract C { - using L for uint256; +library L { + function fExternal(uint256 p, string memory t) external {} + function fInternal(uint256 p, string memory t) internal {} +} +contract Base { + function baseFunctionExternal(uint) external pure {} +} + +contract C is Base { 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) { + function successFunctionArgsIntLiteralTuple() public view returns(bytes memory) { return abi.encodeCall(this.f, (1)); } - function successFunctionArgsIntLiteral() public returns(bytes memory) { + function successFunctionArgsIntLiteral() public view returns(bytes memory) { return abi.encodeCall(this.f, 1); } - function successFunctionArgsLiteralTuple() public returns(bytes memory) { + function successFunctionArgsLiteralTuple() public view returns(bytes memory) { return abi.encodeCall(this.f2, (1, "test")); } - function successFunctionArgsEmptyTuple() public returns(bytes memory) { + function successFunctionArgsEmptyTuple() public view returns(bytes memory) { return abi.encodeCall(this.f4, ()); } + function viaDeclaration() public pure returns (bytes memory) { + return bytes.concat( + abi.encodeCall(Other.fExternal, (1)), + abi.encodeCall(Other.fPublic, (1)), + abi.encodeCall(I.fExternal, (1, "123")) + ); + } + function viaBaseDeclaration() public pure returns (bytes memory) { + return abi.encodeCall(Base.baseFunctionExternal, (1)); + } } // ---- -// 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". diff --git a/test/libsolidity/syntaxTests/specialFunctions/encodeCall_fail_args.sol b/test/libsolidity/syntaxTests/specialFunctions/encodeCall_fail_args.sol new file mode 100644 index 000000000..a1c20f9bd --- /dev/null +++ b/test/libsolidity/syntaxTests/specialFunctions/encodeCall_fail_args.sol @@ -0,0 +1,27 @@ +contract C { + function f(int a) public {} + function f3(int a, int b) public {} + + 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 failFunctionArgsArrayLiteral() public returns(bytes memory) { + return abi.encodeCall(this.f3, [1, 2]); + } +} +// ---- +// TypeError 5407: (181-189): Cannot implicitly convert component at position 0 from "literal_string "test"" to "int256". +// TypeError 7788: (271-301): Expected 1 instead of 2 components for the tuple parameter. +// TypeError 7788: (382-408): Expected 1 instead of 0 components for the tuple parameter. +// TypeError 6219: (489-511): Expected two arguments: a function pointer followed by a tuple. +// TypeError 7515: (597-628): Expected a tuple with 2 components instead of a single non-tuple parameter. +// TypeError 5407: (621-627): Cannot implicitly convert component at position 0 from "uint8[2]" to "int256". diff --git a/test/libsolidity/syntaxTests/specialFunctions/encodeCall_fail_funType.sol b/test/libsolidity/syntaxTests/specialFunctions/encodeCall_fail_funType.sol new file mode 100644 index 000000000..e2de48387 --- /dev/null +++ b/test/libsolidity/syntaxTests/specialFunctions/encodeCall_fail_funType.sol @@ -0,0 +1,78 @@ +interface I { + function fExternal(uint256 p, string memory t) external; +} + +contract Other { + function fExternal(uint) external pure {} + function fPublic(uint) public pure {} + function fInternal(uint) internal pure {} +} + +library L { + function fExternal(uint256 p, string memory t) external {} + function fInternal(uint256 p, string memory t) internal {} +} + +contract Base { + function baseFunctionInternal(uint) internal pure {} + function baseFunctionPublic(uint) public pure {} +} + +function fileLevel(uint) pure {} + +contract C is Base { + using L for uint256; + + function fPublic(int a) public {} + function fInternal(uint256 p, string memory t) internal {} + + function failFunctionPtrMissing() public returns(bytes memory) { + return abi.encodeCall(1, this.fPublic); + } + 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 failLibraryPointerCall() public { + abi.encodeCall(L.fInternal, (1, "123")); + abi.encodeCall(L.fExternal, (1, "123")); + } + function failBoundLibraryPointerCall() public returns (bytes memory) { + uint256 x = 1; + return abi.encodeCall(x.fExternal, (1, "123")); + } + function viaBaseDeclaration() public pure returns (bytes memory) { + return abi.encodeCall(C.fPublic, (2)); + } + function viaBaseDeclaration2() public pure returns (bytes memory) { + return bytes.concat( + abi.encodeCall(Base.baseFunctionPublic, (1)), + abi.encodeCall(Base.baseFunctionInternal, (1)) + ); + } + function fileLevelFunction() public pure returns (bytes memory) { + return abi.encodeCall(fileLevel, (2)); + } + function createFunction() public pure returns (bytes memory) { + return abi.encodeCall(new Other, (2)); + } +} +// ---- +// TypeError 5511: (742-743): Expected first argument to be a function pointer, not "int_const 1". +// TypeError 3509: (855-869): Expected regular external function type, or external view on public function. Cannot use special function. +// TypeError 3509: (982-991): Expected regular external function type, or external view on public function. Provided internal function. +// TypeError 3509: (1187-1207): Expected regular external function type, or external view on public function. Provided internal function. +// TypeError 3509: (1286-1297): Expected regular external function type, or external view on public function. Provided internal function. +// TypeError 3509: (1329-1340): Expected regular external function type, or external view on public function. Cannot use library functions for abi.encodeCall. +// TypeError 3509: (1471-1482): Expected regular external function type, or external view on public function. Cannot use library functions for abi.encodeCall. +// TypeError 3509: (1592-1601): Expected regular external function type, or external view on public function. Provided internal function. Did you forget to prefix "this."? +// TypeError 3509: (1722-1745): Expected regular external function type, or external view on public function. Provided internal function. Functions from base contracts have to be external. +// TypeError 3509: (1771-1796): Expected regular external function type, or external view on public function. Provided internal function. Functions from base contracts have to be external. +// TypeError 3509: (1902-1911): Expected regular external function type, or external view on public function. Provided internal function. +// TypeError 3509: (2010-2019): Expected regular external function type, or external view on public function. Provided creation function.