diff options
-rw-r--r-- | Changelog.md | 1 | ||||
-rw-r--r-- | docs/control-structures.rst | 6 | ||||
-rw-r--r-- | docs/security-considerations.rst | 3 | ||||
-rw-r--r-- | docs/types.rst | 8 | ||||
-rw-r--r-- | docs/units-and-global-variables.rst | 4 | ||||
-rw-r--r-- | libsolidity/analysis/TypeChecker.cpp | 12 | ||||
-rw-r--r-- | libsolidity/ast/Types.cpp | 6 | ||||
-rw-r--r-- | libsolidity/ast/Types.h | 6 | ||||
-rw-r--r-- | libsolidity/codegen/ExpressionCompiler.cpp | 11 | ||||
-rw-r--r-- | test/libsolidity/SolidityEndToEndTest.cpp | 69 | ||||
-rw-r--r-- | test/libsolidity/SolidityNameAndTypeResolution.cpp | 31 | ||||
-rw-r--r-- | test/libsolidity/ViewPureChecker.cpp | 21 |
12 files changed, 161 insertions, 17 deletions
diff --git a/Changelog.md b/Changelog.md index 5fcf134b..5a421fa5 100644 --- a/Changelog.md +++ b/Changelog.md @@ -71,6 +71,7 @@ Breaking Changes: * View Pure Checker: Strictly enfore state mutability. This was already the case in the experimental 0.5.0 mode. Language Features: + * Genreal: Add ``staticcall`` to ``address``. * General: Allow appending ``calldata`` keyword to types, to explicitly specify data location for arguments of external functions. * General: Support ``pop()`` for storage arrays. * General: Scoping rules now follow the C99-style. diff --git a/docs/control-structures.rst b/docs/control-structures.rst index def75132..d0e58908 100644 --- a/docs/control-structures.rst +++ b/docs/control-structures.rst @@ -412,11 +412,11 @@ The deprecated keyword ``throw`` can also be used as an alternative to ``revert( From version 0.4.13 the ``throw`` keyword is deprecated and will be phased out in the future. When exceptions happen in a sub-call, they "bubble up" (i.e. exceptions are rethrown) automatically. Exceptions to this rule are ``send`` -and the low-level functions ``call``, ``delegatecall`` and ``callcode`` -- those return ``false`` in case +and the low-level functions ``call``, ``delegatecall``, ``callcode`` and ``staticcall`` -- those return ``false`` in case of an exception instead of "bubbling up". .. warning:: - The low-level ``call``, ``delegatecall`` and ``callcode`` will return success if the called account is non-existent, as part of the design of EVM. Existence must be checked prior to calling if desired. + The low-level ``call``, ``delegatecall``, ``callcode`` and ``staticcall`` will return success if the called account is non-existent, as part of the design of EVM. Existence must be checked prior to calling if desired. Catching exceptions is not yet possible. @@ -455,7 +455,7 @@ A ``require``-style exception is generated in the following situations: #. Calling ``throw``. #. Calling ``require`` with an argument that evaluates to ``false``. -#. If you call a function via a message call but it does not finish properly (i.e. it runs out of gas, has no matching function, or throws an exception itself), except when a low level operation ``call``, ``send``, ``delegatecall`` or ``callcode`` is used. The low level operations never throw exceptions but indicate failures by returning ``false``. +#. If you call a function via a message call but it does not finish properly (i.e. it runs out of gas, has no matching function, or throws an exception itself), except when a low level operation ``call``, ``send``, ``delegatecall``, ``callcode`` or ``staticcall`` is used. The low level operations never throw exceptions but indicate failures by returning ``false``. #. If you create a contract using the ``new`` keyword but the contract creation does not finish properly (see above for the definition of "not finish properly"). #. If you perform an external function call targeting a contract that contains no code. #. If your contract receives Ether via a public function without ``payable`` modifier (including the constructor and the fallback function). diff --git a/docs/security-considerations.rst b/docs/security-considerations.rst index 5bb3d81d..066a31ea 100644 --- a/docs/security-considerations.rst +++ b/docs/security-considerations.rst @@ -171,7 +171,8 @@ before they interact with your contract. Note that ``.send()`` does **not** throw an exception if the call stack is depleted but rather returns ``false`` in that case. The low-level functions -``.call()``, ``.callcode()`` and ``.delegatecall()`` behave in the same way. +``.call()``, ``.callcode()``, ``.delegatecall()`` and ``.staticcall()`` behave +in the same way. tx.origin ========= diff --git a/docs/types.rst b/docs/types.rst index 6024cbb7..03fd36d9 100644 --- a/docs/types.rst +++ b/docs/types.rst @@ -91,7 +91,7 @@ Operators: defined in the latter. Generally, in floating point almost the entire space is used to represent the number, while only a small number of bits define where the decimal point is. -.. index:: address, balance, send, call, callcode, delegatecall, transfer +.. index:: address, balance, send, call, callcode, delegatecall, staticcall, transfer .. _address: @@ -146,7 +146,7 @@ Send is the low-level counterpart of ``transfer``. If the execution fails, the c to make safe Ether transfers, always check the return value of ``send``, use ``transfer`` or even better: use a pattern where the recipient withdraws the money. -* ``call``, ``callcode`` and ``delegatecall`` +* ``call``, ``callcode``, ``delegatecall`` and ``staticcall`` Furthermore, to interface with contracts that do not adhere to the ABI, or to get more direct control over the encoding, @@ -189,7 +189,9 @@ Lastly, these modifiers can be combined. Their order does not matter:: In a similar way, the function ``delegatecall`` can be used: the difference is that only the code of the given address is used, all other aspects (storage, balance, ...) are taken from the current contract. The purpose of ``delegatecall`` is to use library code which is stored in another contract. The user has to ensure that the layout of storage in both contracts is suitable for delegatecall to be used. Prior to homestead, only a limited variant called ``callcode`` was available that did not provide access to the original ``msg.sender`` and ``msg.value`` values. -All three functions ``call``, ``delegatecall`` and ``callcode`` are very low-level functions and should only be used as a *last resort* as they break the type-safety of Solidity. +Since byzantium ``staticcall`` can be used as well. This is basically the same as ``call``, but will revert, if the called function modifies the state in any way. + +All four functions ``call``, ``delegatecall``, ``callcode`` and ``staticcall`` are very low-level functions and should only be used as a *last resort* as they break the type-safety of Solidity. The ``.gas()`` option is available on all three methods, while the ``.value()`` option is not supported for ``delegatecall``. diff --git a/docs/units-and-global-variables.rst b/docs/units-and-global-variables.rst index 2c571a45..28c9e6ab 100644 --- a/docs/units-and-global-variables.rst +++ b/docs/units-and-global-variables.rst @@ -153,7 +153,7 @@ Mathematical and Cryptographic Functions It might be that you run into Out-of-Gas for ``sha256``, ``ripemd160`` or ``ecrecover`` on a *private blockchain*. The reason for this is that those are implemented as so-called precompiled contracts and these contracts only really exist after they received the first message (although their contract code is hardcoded). Messages to non-existing contracts are more expensive and thus the execution runs into an Out-of-Gas error. A workaround for this problem is to first send e.g. 1 Wei to each of the contracts before you use them in your actual contracts. This is not an issue on the official or test net. -.. index:: balance, send, transfer, call, callcode, delegatecall +.. index:: balance, send, transfer, call, callcode, delegatecall, staticcall .. _address_related: Address Related @@ -171,6 +171,8 @@ Address Related issue low-level ``CALLCODE`` with the given payload, returns ``false`` on failure, forwards all available gas, adjustable ``<address>.delegatecall(bytes memory) returns (bool)``: issue low-level ``DELEGATECALL`` with the given payload, returns ``false`` on failure, forwards all available gas, adjustable +``<address>.staticcall(bytes memory) returns (bool)``: + issue low-level ``STATICCALL`` with the given payload, returns ``false`` on failure, forwards all available gas, adjustable For more information, see the section on :ref:`address`. diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index c737fe41..5033fd63 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -1369,7 +1369,8 @@ void TypeChecker::endVisit(ExpressionStatement const& _statement) if ( kind == FunctionType::Kind::BareCall || kind == FunctionType::Kind::BareCallCode || - kind == FunctionType::Kind::BareDelegateCall + kind == FunctionType::Kind::BareDelegateCall || + kind == FunctionType::Kind::BareStaticCall ) m_errorReporter.warning(_statement.location(), "Return value of low-level calls not used."); else if (kind == FunctionType::Kind::Send) @@ -1764,6 +1765,9 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) return false; } + if (functionType->kind() == FunctionType::Kind::BareStaticCall && !m_evmVersion.hasStaticCall()) + m_errorReporter.typeError(_functionCall.location(), "\"staticcall\" is not supported by the VM version."); + auto returnTypes = allowDynamicTypes ? functionType->returnParameterTypes() : @@ -1844,7 +1848,8 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) else if ( functionType->kind() == FunctionType::Kind::BareCall || functionType->kind() == FunctionType::Kind::BareCallCode || - functionType->kind() == FunctionType::Kind::BareDelegateCall + functionType->kind() == FunctionType::Kind::BareDelegateCall || + functionType->kind() == FunctionType::Kind::BareStaticCall ) { if (arguments.empty()) @@ -1892,7 +1897,8 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) if ( functionType->kind() == FunctionType::Kind::BareCall || functionType->kind() == FunctionType::Kind::BareCallCode || - functionType->kind() == FunctionType::Kind::BareDelegateCall + functionType->kind() == FunctionType::Kind::BareDelegateCall || + functionType->kind() == FunctionType::Kind::BareStaticCall ) msg += " This function requires a single bytes argument. If all your arguments are value types, you can use abi.encode(...) to properly generate it."; else if ( diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index c9dca126..ccf31543 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -621,6 +621,7 @@ MemberList::MemberMap IntegerType::nativeMembers(ContractDefinition const*) cons {"callcode", make_shared<FunctionType>(strings{"bytes memory"}, strings{"bool"}, FunctionType::Kind::BareCallCode, false, StateMutability::Payable)}, {"delegatecall", make_shared<FunctionType>(strings{"bytes memory"}, strings{"bool"}, FunctionType::Kind::BareDelegateCall, false)}, {"send", make_shared<FunctionType>(strings{"uint"}, strings{"bool"}, FunctionType::Kind::Send)}, + {"staticcall", make_shared<FunctionType>(strings{"bytes memory"}, strings{"bool"}, FunctionType::Kind::BareStaticCall, false, StateMutability::View)}, {"transfer", make_shared<FunctionType>(strings{"uint"}, strings(), FunctionType::Kind::Transfer)} }; else @@ -2522,6 +2523,7 @@ string FunctionType::richIdentifier() const case Kind::BareCall: id += "barecall"; break; case Kind::BareCallCode: id += "barecallcode"; break; case Kind::BareDelegateCall: id += "baredelegatecall"; break; + case Kind::BareStaticCall: id += "barestaticcall"; break; case Kind::Creation: id += "creation"; break; case Kind::Send: id += "send"; break; case Kind::Transfer: id += "transfer"; break; @@ -2705,6 +2707,7 @@ unsigned FunctionType::sizeOnStack() const case Kind::BareCall: case Kind::BareCallCode: case Kind::BareDelegateCall: + case Kind::BareStaticCall: case Kind::Internal: case Kind::ArrayPush: case Kind::ArrayPop: @@ -2772,6 +2775,7 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con case Kind::BareCall: case Kind::BareCallCode: case Kind::BareDelegateCall: + case Kind::BareStaticCall: { MemberList::MemberMap members; if (m_kind == Kind::External) @@ -2911,6 +2915,7 @@ bool FunctionType::isBareCall() const case Kind::BareCall: case Kind::BareCallCode: case Kind::BareDelegateCall: + case Kind::BareStaticCall: case Kind::ECRecover: case Kind::SHA256: case Kind::RIPEMD160: @@ -3054,6 +3059,7 @@ bool FunctionType::padArguments() const case Kind::BareCall: case Kind::BareCallCode: case Kind::BareDelegateCall: + case Kind::BareStaticCall: case Kind::SHA256: case Kind::RIPEMD160: case Kind::KECCAK256: diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index d8e73ab9..0b1b5d6d 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -904,6 +904,7 @@ public: BareCall, ///< CALL without function hash BareCallCode, ///< CALLCODE without function hash BareDelegateCall, ///< DELEGATECALL without function hash + BareStaticCall, ///< STATICCALL without function hash Creation, ///< external call using CREATE Send, ///< CALL, but without data and gas Transfer, ///< CALL, but without data and throws on error @@ -935,7 +936,7 @@ public: ABIEncodeWithSelector, ABIEncodeWithSignature, ABIDecode, - GasLeft ///< gasleft() + GasLeft, ///< gasleft() }; virtual Category category() const override { return Category::Function; } @@ -1051,7 +1052,7 @@ public: /// @returns true iff the function type is equal to the given type, ignoring state mutability differences. bool equalExcludingStateMutability(FunctionType const& _other) const; - /// @returns true if the ABI is used for this call (only meaningful for external calls) + /// @returns true if the ABI is NOT used for this call (only meaningful for external calls) bool isBareCall() const; Kind const& kind() const { return m_kind; } StateMutability stateMutability() const { return m_stateMutability; } @@ -1090,6 +1091,7 @@ public: case FunctionType::Kind::BareCall: case FunctionType::Kind::BareCallCode: case FunctionType::Kind::BareDelegateCall: + case FunctionType::Kind::BareStaticCall: return true; default: return false; diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 7a4548f5..9baad7d1 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -571,6 +571,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) case FunctionType::Kind::BareCall: case FunctionType::Kind::BareCallCode: case FunctionType::Kind::BareDelegateCall: + case FunctionType::Kind::BareStaticCall: _functionCall.expression().accept(*this); appendExternalFunctionCall(function, arguments); break; @@ -1172,6 +1173,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) case FunctionType::Kind::BareCall: case FunctionType::Kind::BareCallCode: case FunctionType::Kind::BareDelegateCall: + case FunctionType::Kind::BareStaticCall: case FunctionType::Kind::Transfer: _memberAccess.expression().accept(*this); m_context << funType->externalIdentifier(); @@ -1273,7 +1275,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) ); m_context << Instruction::BALANCE; } - else if ((set<string>{"send", "transfer", "call", "callcode", "delegatecall"}).count(member)) + else if ((set<string>{"send", "transfer", "call", "callcode", "delegatecall", "staticcall"}).count(member)) utils().convertType( *_memberAccess.expression().annotation().type, IntegerType(160, IntegerType::Modifier::Address), @@ -1825,10 +1827,13 @@ void ExpressionCompiler::appendExternalFunctionCall( utils().moveToStackTop(gasValueSize, _functionType.selfType()->sizeOnStack()); auto funKind = _functionType.kind(); - bool returnSuccessCondition = funKind == FunctionType::Kind::BareCall || funKind == FunctionType::Kind::BareCallCode || funKind == FunctionType::Kind::BareDelegateCall; + + solAssert(funKind != FunctionType::Kind::BareStaticCall || m_context.evmVersion().hasStaticCall(), ""); + + bool returnSuccessCondition = funKind == FunctionType::Kind::BareCall || funKind == FunctionType::Kind::BareCallCode || funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::BareStaticCall; bool isCallCode = funKind == FunctionType::Kind::BareCallCode || funKind == FunctionType::Kind::CallCode; bool isDelegateCall = funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::DelegateCall; - bool useStaticCall = _functionType.stateMutability() <= StateMutability::View && m_context.evmVersion().hasStaticCall(); + bool useStaticCall = funKind == FunctionType::Kind::BareStaticCall || (_functionType.stateMutability() <= StateMutability::View && m_context.evmVersion().hasStaticCall()); bool haveReturndatacopy = m_context.evmVersion().supportsReturndata(); unsigned retSize = 0; diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index ce3c4324..2bf20126 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -3592,6 +3592,19 @@ BOOST_AUTO_TEST_CASE(default_fallback_throws) )YY"; compileAndRun(sourceCode); ABI_CHECK(callContractFunction("f()"), encodeArgs(0)); + + if (dev::test::Options::get().evmVersion().hasStaticCall()) + { + char const* sourceCode = R"YY( + contract A { + function f() public returns (bool) { + return address(this).staticcall(""); + } + } + )YY"; + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("f()"), encodeArgs(0)); + } } BOOST_AUTO_TEST_CASE(short_data_calls_fallback) @@ -4418,6 +4431,49 @@ BOOST_AUTO_TEST_CASE(generic_delegatecall) BOOST_CHECK_EQUAL(balanceAt(c_senderAddress), 50 + 11); } +BOOST_AUTO_TEST_CASE(generic_staticcall) +{ + if (dev::test::Options::get().evmVersion().hasStaticCall()) + { + char const* sourceCode = R"**( + contract A { + uint public x; + constructor() public { x = 42; } + function pureFunction(uint256 p) public pure returns (uint256) { return p; } + function viewFunction(uint256 p) public view returns (uint256) { return p + x; } + function nonpayableFunction(uint256 p) public returns (uint256) { x = p; return x; } + function assertFunction(uint256 p) public view returns (uint256) { assert(x == p); return x; } + } + contract C { + function f(address a) public view returns (bool) + { + return a.staticcall(abi.encodeWithSignature("pureFunction(uint256)", 23)); + } + function g(address a) public view returns (bool) + { + return a.staticcall(abi.encodeWithSignature("viewFunction(uint256)", 23)); + } + function h(address a) public view returns (bool) + { + return a.staticcall(abi.encodeWithSignature("nonpayableFunction(uint256)", 23)); + } + function i(address a, uint256 v) public view returns (bool) + { + return a.staticcall(abi.encodeWithSignature("assertFunction(uint256)", v)); + } + } + )**"; + compileAndRun(sourceCode, 0, "A"); + u160 const c_addressA = m_contractAddress; + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f(address)", c_addressA), encodeArgs(true)); + ABI_CHECK(callContractFunction("g(address)", c_addressA), encodeArgs(true)); + ABI_CHECK(callContractFunction("h(address)", c_addressA), encodeArgs(false)); + ABI_CHECK(callContractFunction("i(address,uint256)", c_addressA, 42), encodeArgs(true)); + ABI_CHECK(callContractFunction("i(address,uint256)", c_addressA, 23), encodeArgs(false)); + } +} + BOOST_AUTO_TEST_CASE(library_call_in_homestead) { char const* sourceCode = R"( @@ -12419,6 +12475,19 @@ BOOST_AUTO_TEST_CASE(bare_call_invalid_address) compileAndRun(sourceCode, 0, "C"); ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(1))); ABI_CHECK(callContractFunction("h()"), encodeArgs(u256(1))); + + if (dev::test::Options::get().evmVersion().hasStaticCall()) + { + char const* sourceCode = R"YY( + contract C { + function f() external returns (bool) { + return address(0x4242).staticcall(""); + } + } + )YY"; + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(1))); + } } BOOST_AUTO_TEST_CASE(delegatecall_return_value) diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index 55e81867..387505a5 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -433,6 +433,37 @@ BOOST_AUTO_TEST_CASE(getter_is_memory_type) } } +BOOST_AUTO_TEST_CASE(address_staticcall) +{ + char const* sourceCode = R"( + contract C { + function f() public view returns(bool) { + return address(0x4242).staticcall(""); + } + } + )"; + + if (dev::test::Options::get().evmVersion().hasStaticCall()) + CHECK_SUCCESS_NO_WARNINGS(sourceCode); + else + CHECK_ERROR(sourceCode, TypeError, "\"staticcall\" is not supported by the VM version."); +} + +BOOST_AUTO_TEST_CASE(address_staticcall_value) +{ + if (dev::test::Options::get().evmVersion().hasStaticCall()) + { + char const* sourceCode = R"( + contract C { + function f() public view { + address(0x4242).staticcall.value; + } + } + )"; + CHECK_ERROR(sourceCode, TypeError, "Member \"value\" not found or not visible after argument-dependent lookup"); + } +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/ViewPureChecker.cpp b/test/libsolidity/ViewPureChecker.cpp index 299cd084..d993b92e 100644 --- a/test/libsolidity/ViewPureChecker.cpp +++ b/test/libsolidity/ViewPureChecker.cpp @@ -53,8 +53,11 @@ BOOST_AUTO_TEST_CASE(environment_access) "tx.origin", "tx.gasprice", "this", - "address(1).balance" + "address(1).balance", }; + if (dev::test::Options::get().evmVersion().hasStaticCall()) + view.emplace_back("address(0x4242).staticcall(\"\")"); + // ``block.blockhash`` and ``blockhash`` are tested separately below because their usage will // produce warnings that can't be handled in a generic way. vector<string> pure{ @@ -95,6 +98,22 @@ BOOST_AUTO_TEST_CASE(environment_access) ); } +BOOST_AUTO_TEST_CASE(address_staticcall) +{ + string text = R"( + contract C { + function i() view public returns (bool) { + return address(0x4242).staticcall(""); + } + } + )"; + if (!dev::test::Options::get().evmVersion().hasStaticCall()) + CHECK_ERROR(text, TypeError, "\"staticcall\" is not supported by the VM version."); + else + CHECK_SUCCESS_NO_WARNINGS(text); +} + + BOOST_AUTO_TEST_CASE(assembly_staticcall) { string text = R"( |