Merge pull request #8763 from ethereum/bareCall

IR code generation for call.
This commit is contained in:
chriseth 2020-05-11 16:06:54 +02:00 committed by GitHub
commit f1e0aa7a56
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 136 additions and 75 deletions

View File

@ -706,10 +706,12 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
}
case FunctionType::Kind::External:
case FunctionType::Kind::DelegateCall:
appendExternalFunctionCall(_functionCall, arguments);
break;
case FunctionType::Kind::BareCall:
case FunctionType::Kind::BareDelegateCall:
case FunctionType::Kind::BareStaticCall:
appendExternalFunctionCall(_functionCall, arguments);
appendBareCall(_functionCall, arguments);
break;
case FunctionType::Kind::BareCallCode:
solAssert(false, "Callcode has been removed.");
@ -1755,18 +1757,20 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
)
{
FunctionType const& funType = dynamic_cast<FunctionType const&>(type(_functionCall.expression()));
solAssert(
funType.takesArbitraryParameters() ||
_arguments.size() == funType.parameterTypes().size(), ""
);
solUnimplementedAssert(!funType.bound(), "");
solAssert(!funType.takesArbitraryParameters(), "");
solAssert(_arguments.size() == funType.parameterTypes().size(), "");
solAssert(!funType.isBareCall(), "");
FunctionType::Kind const funKind = funType.kind();
solAssert(funKind != FunctionType::Kind::BareStaticCall || m_context.evmVersion().hasStaticCall(), "");
solAssert(funKind != FunctionType::Kind::BareCallCode, "Callcode has been removed.");
solAssert(
funKind == FunctionType::Kind::External || funKind == FunctionType::Kind::DelegateCall,
"Can only be used for regular external calls."
);
bool const isDelegateCall = funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::DelegateCall;
bool const useStaticCall = funKind == FunctionType::Kind::BareStaticCall || (funType.stateMutability() <= StateMutability::View && m_context.evmVersion().hasStaticCall());
solUnimplementedAssert(!funType.bound(), "");
bool const isDelegateCall = funKind == FunctionType::Kind::DelegateCall;
bool const useStaticCall = funType.stateMutability() <= StateMutability::View && m_context.evmVersion().hasStaticCall();
ReturnInfo const returnInfo{m_context.evmVersion(), funType};
@ -1778,7 +1782,6 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
argumentStrings += IRVariable(*arg).stackSlots();
}
solUnimplementedAssert(funKind != FunctionType::Kind::ECRecover, "");
if (!m_context.evmVersion().canOverchargeGasForCall())
{
@ -1790,33 +1793,19 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
m_code << "mstore(add(" << freeMemory() << ", " << to_string(returnInfo.estimatedReturnSize) << "), 0)\n";
}
ABIFunctions abi(m_context.evmVersion(), m_context.revertStrings(), m_context.functionCollector());
Whiskers templ(R"(
<?checkExistence>
if iszero(extcodesize(<address>)) { revert(0, 0) }
</checkExistence>
if iszero(extcodesize(<address>)) { revert(0, 0) }
// storage for arguments and returned data
let <pos> := <freeMemory>
<?bareCall>
<!bareCall>
mstore(<pos>, <shl28>(<funId>))
</bareCall>
let <end> := <encodeArgs>(
<?bareCall>
<pos>
<!bareCall>
add(<pos>, 4)
</bareCall>
<argumentString>
)
mstore(<pos>, <shl28>(<funId>))
let <end> := <encodeArgs>(add(<pos>, 4) <argumentString>)
let <success> := <call>(<gas>, <address>, <?hasValue> <value>, </hasValue> <pos>, sub(<end>, <pos>), <pos>, <reservedReturnSize>)
<?noTryCall>
if iszero(<success>) { <forwardingRevert>() }
</noTryCall>
<?hasRetVars> let <retVars> </hasRetVars>
<?+retVars> let <retVars> </+retVars>
if <success> {
<?dynamicReturnSize>
// copy dynamic return data out
@ -1827,12 +1816,11 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
mstore(<freeMemoryPointer>, add(<pos>, <roundUp>(<returnSize>)))
// decode return parameters from external try-call into retVars
<?hasRetVars> <retVars> := </hasRetVars> <abiDecode>(<pos>, add(<pos>, <returnSize>))
<?+retVars> <retVars> := </+retVars> <abiDecode>(<pos>, add(<pos>, <returnSize>))
}
)");
templ("pos", m_context.newYulVariable());
templ("end", m_context.newYulVariable());
templ("bareCall", funType.isBareCall());
if (_functionCall.annotation().tryCall)
templ("success", m_context.trySuccessConditionVariable(_functionCall));
else
@ -1840,17 +1828,8 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
templ("freeMemory", freeMemory());
templ("shl28", m_utils.shiftLeftFunction(8 * (32 - 4)));
if (!funType.isBareCall())
templ("funId", IRVariable(_functionCall.expression()).part("functionIdentifier").name());
if (funKind == FunctionType::Kind::ECRecover)
templ("address", "1");
else if (funKind == FunctionType::Kind::SHA256)
templ("address", "2");
else if (funKind == FunctionType::Kind::RIPEMD160)
templ("address", "3");
else
templ("address", IRVariable(_functionCall.expression()).part("address").name());
templ("funId", IRVariable(_functionCall.expression()).part("functionIdentifier").name());
templ("address", IRVariable(_functionCall.expression()).part("address").name());
// Always use the actual return length, and not our calculated expected length, if returndatacopy is supported.
// This ensures it can catch badly formatted input from external calls.
@ -1863,50 +1842,27 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
string const retVars = IRVariable(_functionCall).commaSeparatedList();
templ("retVars", retVars);
templ("hasRetVars", !retVars.empty());
solAssert(retVars.empty() == returnInfo.returnTypes.empty(), "");
templ("roundUp", m_utils.roundUpFunction());
templ("abiDecode", abi.tupleDecoder(returnInfo.returnTypes, true));
templ("abiDecode", m_context.abiFunctions().tupleDecoder(returnInfo.returnTypes, true));
templ("dynamicReturnSize", returnInfo.dynamicReturnSize);
templ("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer));
templ("noTryCall", !_functionCall.annotation().tryCall);
// If the function takes arbitrary parameters or is a bare call, copy dynamic length data in place.
// Move arguments to memory, will not update the free memory pointer (but will update the memory
// pointer on the stack).
bool encodeInPlace = funType.takesArbitraryParameters() || funType.isBareCall();
if (funType.kind() == FunctionType::Kind::ECRecover)
// This would be the only combination of padding and in-place encoding,
// but all parameters of ecrecover are value types anyway.
encodeInPlace = false;
bool encodeForLibraryCall = funKind == FunctionType::Kind::DelegateCall;
solUnimplementedAssert(encodeInPlace == !funType.padArguments(), "");
if (encodeInPlace)
{
solUnimplementedAssert(!encodeForLibraryCall, "");
templ("encodeArgs", abi.tupleEncoderPacked(argumentTypes, funType.parameterTypes()));
}
else
templ("encodeArgs", abi.tupleEncoder(argumentTypes, funType.parameterTypes(), encodeForLibraryCall));
solAssert(funType.padArguments(), "");
templ("encodeArgs", m_context.abiFunctions().tupleEncoder(argumentTypes, funType.parameterTypes(), encodeForLibraryCall));
templ("argumentString", joinHumanReadablePrefixed(argumentStrings));
// Output data will replace input data, unless we have ECRecover (then, output
// area will be 32 bytes just before input area).
solUnimplementedAssert(funKind != FunctionType::Kind::ECRecover, "");
solAssert(!isDelegateCall || !funType.valueSet(), "Value set for delegatecall");
solAssert(!useStaticCall || !funType.valueSet(), "Value set for staticcall");
templ("hasValue", !isDelegateCall && !useStaticCall);
templ("value", funType.valueSet() ? IRVariable(_functionCall.expression()).part("value").name() : "0");
// Check that the target contract exists (has code) for non-low-level calls.
bool checkExistence = (funKind == FunctionType::Kind::External || funKind == FunctionType::Kind::DelegateCall);
templ("checkExistence", checkExistence);
if (funType.gasSet())
templ("gas", IRVariable(_functionCall.expression()).part("gas").name());
else if (m_context.evmVersion().canOverchargeGasForCall())
@ -1919,8 +1875,6 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
u256 gasNeededByCaller = evmasm::GasCosts::callGas(m_context.evmVersion()) + 10;
if (funType.valueSet())
gasNeededByCaller += evmasm::GasCosts::callValueTransferGas;
if (!checkExistence)
gasNeededByCaller += evmasm::GasCosts::callNewAccountGas; // we never know
templ("gas", "sub(gas(), " + formatNumber(gasNeededByCaller) + ")");
}
// Order is important here, STATICCALL might overlap with DELEGATECALL.
@ -1933,8 +1887,103 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
templ("forwardingRevert", m_utils.forwardingRevertFunction());
solUnimplementedAssert(funKind != FunctionType::Kind::RIPEMD160, "");
solUnimplementedAssert(funKind != FunctionType::Kind::ECRecover, "");
m_code << templ.render();
}
void IRGeneratorForStatements::appendBareCall(
FunctionCall const& _functionCall,
vector<ASTPointer<Expression const>> const& _arguments
)
{
FunctionType const& funType = dynamic_cast<FunctionType const&>(type(_functionCall.expression()));
solAssert(
!funType.bound() &&
!funType.takesArbitraryParameters() &&
_arguments.size() == 1 &&
funType.parameterTypes().size() == 1, ""
);
FunctionType::Kind const funKind = funType.kind();
solAssert(funKind != FunctionType::Kind::BareStaticCall || m_context.evmVersion().hasStaticCall(), "");
solAssert(funKind != FunctionType::Kind::BareCallCode, "Callcode has been removed.");
solAssert(
funKind == FunctionType::Kind::BareCall ||
funKind == FunctionType::Kind::BareDelegateCall ||
funKind == FunctionType::Kind::BareStaticCall, ""
);
solAssert(!_functionCall.annotation().tryCall, "");
Whiskers templ(R"(
<?needsEncoding>
let <pos> := mload(<freeMemoryPointer>)
let <length> := sub(<encode>(<pos> <?+arg>,</+arg> <arg>), <pos>)
<!needsEncoding>
let <pos> := add(<arg>, 0x20)
let <length> := mload(<arg>)
</needsEncoding>
let <success> := <call>(<gas>, <address>, <?+value> <value>, </+value> <pos>, <length>, 0, 0)
<?+returndataVar>
let <returndataVar> := <extractReturndataFunction>()
</+returndataVar>
)");
templ("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer));
templ("pos", m_context.newYulVariable());
templ("length", m_context.newYulVariable());
templ("arg", IRVariable(*_arguments.front()).commaSeparatedList());
Type const& argType = type(*_arguments.front());
if (argType == *TypeProvider::bytesMemory() || argType == *TypeProvider::stringMemory())
templ("needsEncoding", false);
else
{
templ("needsEncoding", true);
ABIFunctions abi(m_context.evmVersion(), m_context.revertStrings(), m_context.functionCollector());
templ("encode", abi.tupleEncoderPacked({&argType}, {TypeProvider::bytesMemory()}));
}
templ("success", IRVariable(_functionCall).tupleComponent(0).name());
if (IRVariable(_functionCall).tupleComponent(1).type().category() == Type::Category::InaccessibleDynamic)
templ("returndataVar", "");
else
{
templ("returndataVar", IRVariable(_functionCall).tupleComponent(1).part("mpos").name());
templ("extractReturndataFunction", m_utils.extractReturndataFunction());
}
templ("address", IRVariable(_functionCall.expression()).part("address").name());
if (funKind == FunctionType::Kind::BareCall)
{
templ("value", funType.valueSet() ? IRVariable(_functionCall.expression()).part("value").name() : "0");
templ("call", "call");
}
else
{
solAssert(!funType.valueSet(), "Value set for delegatecall or staticcall.");
templ("value", "");
if (funKind == FunctionType::Kind::BareStaticCall)
templ("call", "staticcall");
else
templ("call", "delegatecall");
}
if (funType.gasSet())
templ("gas", IRVariable(_functionCall.expression()).part("gas").name());
else if (m_context.evmVersion().canOverchargeGasForCall())
// Send all gas (requires tangerine whistle EVM)
templ("gas", "gas()");
else
{
// send all gas except the amount needed to execute "SUB" and "CALL"
// @todo this retains too much gas for now, needs to be fine-tuned.
u256 gasNeededByCaller = evmasm::GasCosts::callGas(m_context.evmVersion()) + 10;
if (funType.valueSet())
gasNeededByCaller += evmasm::GasCosts::callValueTransferGas;
gasNeededByCaller += evmasm::GasCosts::callNewAccountGas; // we never know
templ("gas", "sub(gas(), " + formatNumber(gasNeededByCaller) + ")");
}
m_code << templ.render();
}

View File

@ -106,6 +106,13 @@ private:
std::vector<ASTPointer<Expression const>> const& _arguments
);
/// Appends code for .call / .delegatecall / .staticcall.
/// All involved expressions have already been visited.
void appendBareCall(
FunctionCall const& _functionCall,
std::vector<ASTPointer<Expression const>> const& _arguments
);
/// @returns code that evaluates to the first unused memory slot (which does not have to
/// be empty).
static std::string freeMemory();

View File

@ -54,7 +54,7 @@ IRVariable IRVariable::part(string const& _name) const
solAssert(itemName.empty() || itemType, "");
return IRVariable{suffixedName(itemName), itemType ? *itemType : m_type};
}
solAssert(false, "Invalid stack item name.");
solAssert(false, "Invalid stack item name: " + _name);
}
vector<string> IRVariable::stackSlots() const

View File

@ -22,5 +22,7 @@ contract C {
}
}
// ====
// compileViaYul: also
// ----
// f() -> true

View File

@ -3,7 +3,5 @@ contract C {
return sha256("");
}
}
// ====
// compileViaYul: also
// ----
// f() -> 0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

View File

@ -14,6 +14,8 @@ contract B {
}
}
// ====
// compileViaYul: also
// ----
// testIt() ->
// test() -> 2

View File

@ -22,6 +22,7 @@ contract C {
}
}
// ====
// compileViaYul: also
// EVMVersion: >=byzantium
// ----
// get() -> 0x00

View File

@ -24,6 +24,7 @@ contract C {
}
}
// ====
// compileViaYul: also
// EVMVersion: <byzantium
// ----
// get() -> 0x00

View File

@ -32,6 +32,7 @@ contract D {
}
}
// ====
// compileViaYul: also
// EVMVersion: <byzantium
// ----
// f() -> 0x1