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. * View Pure Checker: Strictly enfore state mutability. This was already the case in the experimental 0.5.0 mode.
Language Features: 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: Allow appending ``calldata`` keyword to types, to explicitly specify data location for arguments of external functions.
* General: Support ``pop()`` for storage arrays. * General: Support ``pop()`` for storage arrays.
* General: Scoping rules now follow the C99-style. * 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. 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:
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 issue low-level ``CALLCODE`` with the given payload, returns ``false`` on failure, forwards all available gas, adjustable
``<address>.delegatecall(bytes memory) returns (bool)``: ``<address>.delegatecall(bytes memory) returns (bool)``:
issue low-level ``DELEGATECALL`` with the given payload, returns ``false`` on failure, forwards all available gas, adjustable 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`. For more information, see the section on :ref:`address`.

View File

@ -1359,7 +1359,8 @@ void TypeChecker::endVisit(ExpressionStatement const& _statement)
if ( if (
kind == FunctionType::Kind::BareCall || kind == FunctionType::Kind::BareCall ||
kind == FunctionType::Kind::BareCallCode || 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."); m_errorReporter.warning(_statement.location(), "Return value of low-level calls not used.");
else if (kind == FunctionType::Kind::Send) else if (kind == FunctionType::Kind::Send)
@ -1754,6 +1755,9 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
return false; 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 = auto returnTypes =
allowDynamicTypes ? allowDynamicTypes ?
functionType->returnParameterTypes() : functionType->returnParameterTypes() :
@ -1834,7 +1838,8 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
else if ( else if (
functionType->kind() == FunctionType::Kind::BareCall || functionType->kind() == FunctionType::Kind::BareCall ||
functionType->kind() == FunctionType::Kind::BareCallCode || functionType->kind() == FunctionType::Kind::BareCallCode ||
functionType->kind() == FunctionType::Kind::BareDelegateCall functionType->kind() == FunctionType::Kind::BareDelegateCall ||
functionType->kind() == FunctionType::Kind::BareStaticCall
) )
{ {
if (arguments.empty()) if (arguments.empty())
@ -1882,7 +1887,8 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
if ( if (
functionType->kind() == FunctionType::Kind::BareCall || functionType->kind() == FunctionType::Kind::BareCall ||
functionType->kind() == FunctionType::Kind::BareCallCode || 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."; 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 ( 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)}, {"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)}, {"delegatecall", make_shared<FunctionType>(strings{"bytes memory"}, strings{"bool"}, FunctionType::Kind::BareDelegateCall, false)},
{"send", make_shared<FunctionType>(strings{"uint"}, strings{"bool"}, FunctionType::Kind::Send)}, {"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)} {"transfer", make_shared<FunctionType>(strings{"uint"}, strings(), FunctionType::Kind::Transfer)}
}; };
else else
@ -2522,6 +2523,7 @@ string FunctionType::richIdentifier() const
case Kind::BareCall: id += "barecall"; break; case Kind::BareCall: id += "barecall"; break;
case Kind::BareCallCode: id += "barecallcode"; break; case Kind::BareCallCode: id += "barecallcode"; break;
case Kind::BareDelegateCall: id += "baredelegatecall"; break; case Kind::BareDelegateCall: id += "baredelegatecall"; break;
case Kind::BareStaticCall: id += "barestaticcall"; break;
case Kind::Creation: id += "creation"; break; case Kind::Creation: id += "creation"; break;
case Kind::Send: id += "send"; break; case Kind::Send: id += "send"; break;
case Kind::Transfer: id += "transfer"; break; case Kind::Transfer: id += "transfer"; break;
@ -2705,6 +2707,7 @@ unsigned FunctionType::sizeOnStack() const
case Kind::BareCall: case Kind::BareCall:
case Kind::BareCallCode: case Kind::BareCallCode:
case Kind::BareDelegateCall: case Kind::BareDelegateCall:
case Kind::BareStaticCall:
case Kind::Internal: case Kind::Internal:
case Kind::ArrayPush: case Kind::ArrayPush:
case Kind::ArrayPop: case Kind::ArrayPop:
@ -2772,6 +2775,7 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con
case Kind::BareCall: case Kind::BareCall:
case Kind::BareCallCode: case Kind::BareCallCode:
case Kind::BareDelegateCall: case Kind::BareDelegateCall:
case Kind::BareStaticCall:
{ {
MemberList::MemberMap members; MemberList::MemberMap members;
if (m_kind == Kind::External) if (m_kind == Kind::External)
@ -2911,6 +2915,7 @@ bool FunctionType::isBareCall() const
case Kind::BareCall: case Kind::BareCall:
case Kind::BareCallCode: case Kind::BareCallCode:
case Kind::BareDelegateCall: case Kind::BareDelegateCall:
case Kind::BareStaticCall:
case Kind::ECRecover: case Kind::ECRecover:
case Kind::SHA256: case Kind::SHA256:
case Kind::RIPEMD160: case Kind::RIPEMD160:
@ -3054,6 +3059,7 @@ bool FunctionType::padArguments() const
case Kind::BareCall: case Kind::BareCall:
case Kind::BareCallCode: case Kind::BareCallCode:
case Kind::BareDelegateCall: case Kind::BareDelegateCall:
case Kind::BareStaticCall:
case Kind::SHA256: case Kind::SHA256:
case Kind::RIPEMD160: case Kind::RIPEMD160:
case Kind::KECCAK256: case Kind::KECCAK256:

View File

@ -904,6 +904,7 @@ public:
BareCall, ///< CALL without function hash BareCall, ///< CALL without function hash
BareCallCode, ///< CALLCODE without function hash BareCallCode, ///< CALLCODE without function hash
BareDelegateCall, ///< DELEGATECALL without function hash BareDelegateCall, ///< DELEGATECALL without function hash
BareStaticCall, ///< STATICCALL without function hash
Creation, ///< external call using CREATE Creation, ///< external call using CREATE
Send, ///< CALL, but without data and gas Send, ///< CALL, but without data and gas
Transfer, ///< CALL, but without data and throws on error Transfer, ///< CALL, but without data and throws on error
@ -935,7 +936,7 @@ public:
ABIEncodeWithSelector, ABIEncodeWithSelector,
ABIEncodeWithSignature, ABIEncodeWithSignature,
ABIDecode, ABIDecode,
GasLeft ///< gasleft() GasLeft, ///< gasleft()
}; };
virtual Category category() const override { return Category::Function; } 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. /// @returns true iff the function type is equal to the given type, ignoring state mutability differences.
bool equalExcludingStateMutability(FunctionType const& _other) const; 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; bool isBareCall() const;
Kind const& kind() const { return m_kind; } Kind const& kind() const { return m_kind; }
StateMutability stateMutability() const { return m_stateMutability; } StateMutability stateMutability() const { return m_stateMutability; }
@ -1090,6 +1091,7 @@ public:
case FunctionType::Kind::BareCall: case FunctionType::Kind::BareCall:
case FunctionType::Kind::BareCallCode: case FunctionType::Kind::BareCallCode:
case FunctionType::Kind::BareDelegateCall: case FunctionType::Kind::BareDelegateCall:
case FunctionType::Kind::BareStaticCall:
return true; return true;
default: default:
return false; return false;

View File

@ -571,6 +571,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
case FunctionType::Kind::BareCall: case FunctionType::Kind::BareCall:
case FunctionType::Kind::BareCallCode: case FunctionType::Kind::BareCallCode:
case FunctionType::Kind::BareDelegateCall: case FunctionType::Kind::BareDelegateCall:
case FunctionType::Kind::BareStaticCall:
_functionCall.expression().accept(*this); _functionCall.expression().accept(*this);
appendExternalFunctionCall(function, arguments); appendExternalFunctionCall(function, arguments);
break; break;
@ -1172,6 +1173,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
case FunctionType::Kind::BareCall: case FunctionType::Kind::BareCall:
case FunctionType::Kind::BareCallCode: case FunctionType::Kind::BareCallCode:
case FunctionType::Kind::BareDelegateCall: case FunctionType::Kind::BareDelegateCall:
case FunctionType::Kind::BareStaticCall:
case FunctionType::Kind::Transfer: case FunctionType::Kind::Transfer:
_memberAccess.expression().accept(*this); _memberAccess.expression().accept(*this);
m_context << funType->externalIdentifier(); m_context << funType->externalIdentifier();
@ -1273,7 +1275,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
); );
m_context << Instruction::BALANCE; 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( utils().convertType(
*_memberAccess.expression().annotation().type, *_memberAccess.expression().annotation().type,
IntegerType(160, IntegerType::Modifier::Address), IntegerType(160, IntegerType::Modifier::Address),
@ -1825,10 +1827,13 @@ void ExpressionCompiler::appendExternalFunctionCall(
utils().moveToStackTop(gasValueSize, _functionType.selfType()->sizeOnStack()); utils().moveToStackTop(gasValueSize, _functionType.selfType()->sizeOnStack());
auto funKind = _functionType.kind(); 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 isCallCode = funKind == FunctionType::Kind::BareCallCode || funKind == FunctionType::Kind::CallCode;
bool isDelegateCall = funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::DelegateCall; 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(); bool haveReturndatacopy = m_context.evmVersion().supportsReturndata();
unsigned retSize = 0; unsigned retSize = 0;

View File

@ -3592,6 +3592,19 @@ BOOST_AUTO_TEST_CASE(default_fallback_throws)
)YY"; )YY";
compileAndRun(sourceCode); compileAndRun(sourceCode);
ABI_CHECK(callContractFunction("f()"), encodeArgs(0)); 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) 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_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) BOOST_AUTO_TEST_CASE(library_call_in_homestead)
{ {
char const* sourceCode = R"( char const* sourceCode = R"(
@ -12216,6 +12272,19 @@ BOOST_AUTO_TEST_CASE(bare_call_invalid_address)
compileAndRun(sourceCode, 0, "C"); compileAndRun(sourceCode, 0, "C");
ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(1))); ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(1)));
ABI_CHECK(callContractFunction("h()"), 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) 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() BOOST_AUTO_TEST_SUITE_END()
} }