Add `staticcall to address`.

This commit is contained in:
Daniel Kirchner 2018-08-15 14:40:20 +02:00 committed by chriseth
parent 2ed793c4d3
commit 7ca0aaaf6f
8 changed files with 116 additions and 9 deletions

View File

@ -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.

View File

@ -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`.

View File

@ -1359,7 +1359,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)
@ -1754,6 +1755,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() :
@ -1834,7 +1838,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())
@ -1882,7 +1887,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 (

View File

@ -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:

View File

@ -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;

View File

@ -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;

View File

@ -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)
@ -4215,6 +4228,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"(
@ -12216,6 +12272,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)

View File

@ -433,6 +433,22 @@ 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_SUITE_END()
}