Support bare calls.

This commit is contained in:
chriseth 2020-05-04 12:50:31 +02:00
parent 4c13ce24f0
commit c0bf529236
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::External:
case FunctionType::Kind::DelegateCall: case FunctionType::Kind::DelegateCall:
appendExternalFunctionCall(_functionCall, arguments);
break;
case FunctionType::Kind::BareCall: case FunctionType::Kind::BareCall:
case FunctionType::Kind::BareDelegateCall: case FunctionType::Kind::BareDelegateCall:
case FunctionType::Kind::BareStaticCall: case FunctionType::Kind::BareStaticCall:
appendExternalFunctionCall(_functionCall, arguments); appendBareCall(_functionCall, arguments);
break; break;
case FunctionType::Kind::BareCallCode: case FunctionType::Kind::BareCallCode:
solAssert(false, "Callcode has been removed."); solAssert(false, "Callcode has been removed.");
@ -1755,18 +1757,20 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
) )
{ {
FunctionType const& funType = dynamic_cast<FunctionType const&>(type(_functionCall.expression())); FunctionType const& funType = dynamic_cast<FunctionType const&>(type(_functionCall.expression()));
solAssert( solAssert(!funType.takesArbitraryParameters(), "");
funType.takesArbitraryParameters() || solAssert(_arguments.size() == funType.parameterTypes().size(), "");
_arguments.size() == funType.parameterTypes().size(), "" solAssert(!funType.isBareCall(), "");
);
solUnimplementedAssert(!funType.bound(), "");
FunctionType::Kind const funKind = funType.kind(); FunctionType::Kind const funKind = funType.kind();
solAssert(funKind != FunctionType::Kind::BareStaticCall || m_context.evmVersion().hasStaticCall(), ""); solAssert(
solAssert(funKind != FunctionType::Kind::BareCallCode, "Callcode has been removed."); 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; solUnimplementedAssert(!funType.bound(), "");
bool const useStaticCall = funKind == FunctionType::Kind::BareStaticCall || (funType.stateMutability() <= StateMutability::View && m_context.evmVersion().hasStaticCall());
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}; ReturnInfo const returnInfo{m_context.evmVersion(), funType};
@ -1778,7 +1782,6 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
argumentStrings += IRVariable(*arg).stackSlots(); argumentStrings += IRVariable(*arg).stackSlots();
} }
solUnimplementedAssert(funKind != FunctionType::Kind::ECRecover, "");
if (!m_context.evmVersion().canOverchargeGasForCall()) if (!m_context.evmVersion().canOverchargeGasForCall())
{ {
@ -1790,33 +1793,19 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
m_code << "mstore(add(" << freeMemory() << ", " << to_string(returnInfo.estimatedReturnSize) << "), 0)\n"; 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"( Whiskers templ(R"(
<?checkExistence>
if iszero(extcodesize(<address>)) { revert(0, 0) } if iszero(extcodesize(<address>)) { revert(0, 0) }
</checkExistence>
// storage for arguments and returned data // storage for arguments and returned data
let <pos> := <freeMemory> let <pos> := <freeMemory>
<?bareCall>
<!bareCall>
mstore(<pos>, <shl28>(<funId>)) mstore(<pos>, <shl28>(<funId>))
</bareCall> let <end> := <encodeArgs>(add(<pos>, 4) <argumentString>)
let <end> := <encodeArgs>(
<?bareCall>
<pos>
<!bareCall>
add(<pos>, 4)
</bareCall>
<argumentString>
)
let <success> := <call>(<gas>, <address>, <?hasValue> <value>, </hasValue> <pos>, sub(<end>, <pos>), <pos>, <reservedReturnSize>) let <success> := <call>(<gas>, <address>, <?hasValue> <value>, </hasValue> <pos>, sub(<end>, <pos>), <pos>, <reservedReturnSize>)
<?noTryCall> <?noTryCall>
if iszero(<success>) { <forwardingRevert>() } if iszero(<success>) { <forwardingRevert>() }
</noTryCall> </noTryCall>
<?hasRetVars> let <retVars> </hasRetVars> <?+retVars> let <retVars> </+retVars>
if <success> { if <success> {
<?dynamicReturnSize> <?dynamicReturnSize>
// copy dynamic return data out // copy dynamic return data out
@ -1827,12 +1816,11 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
mstore(<freeMemoryPointer>, add(<pos>, <roundUp>(<returnSize>))) mstore(<freeMemoryPointer>, add(<pos>, <roundUp>(<returnSize>)))
// decode return parameters from external try-call into retVars // 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("pos", m_context.newYulVariable());
templ("end", m_context.newYulVariable()); templ("end", m_context.newYulVariable());
templ("bareCall", funType.isBareCall());
if (_functionCall.annotation().tryCall) if (_functionCall.annotation().tryCall)
templ("success", m_context.trySuccessConditionVariable(_functionCall)); templ("success", m_context.trySuccessConditionVariable(_functionCall));
else else
@ -1840,16 +1828,7 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
templ("freeMemory", freeMemory()); templ("freeMemory", freeMemory());
templ("shl28", m_utils.shiftLeftFunction(8 * (32 - 4))); templ("shl28", m_utils.shiftLeftFunction(8 * (32 - 4)));
if (!funType.isBareCall())
templ("funId", IRVariable(_functionCall.expression()).part("functionIdentifier").name()); 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("address", IRVariable(_functionCall.expression()).part("address").name());
// Always use the actual return length, and not our calculated expected length, if returndatacopy is supported. // Always use the actual return length, and not our calculated expected length, if returndatacopy is supported.
@ -1863,50 +1842,27 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
string const retVars = IRVariable(_functionCall).commaSeparatedList(); string const retVars = IRVariable(_functionCall).commaSeparatedList();
templ("retVars", retVars); templ("retVars", retVars);
templ("hasRetVars", !retVars.empty());
solAssert(retVars.empty() == returnInfo.returnTypes.empty(), ""); solAssert(retVars.empty() == returnInfo.returnTypes.empty(), "");
templ("roundUp", m_utils.roundUpFunction()); 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("dynamicReturnSize", returnInfo.dynamicReturnSize);
templ("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer)); templ("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer));
templ("noTryCall", !_functionCall.annotation().tryCall); 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; bool encodeForLibraryCall = funKind == FunctionType::Kind::DelegateCall;
solUnimplementedAssert(encodeInPlace == !funType.padArguments(), ""); solAssert(funType.padArguments(), "");
if (encodeInPlace) templ("encodeArgs", m_context.abiFunctions().tupleEncoder(argumentTypes, funType.parameterTypes(), encodeForLibraryCall));
{
solUnimplementedAssert(!encodeForLibraryCall, "");
templ("encodeArgs", abi.tupleEncoderPacked(argumentTypes, funType.parameterTypes()));
}
else
templ("encodeArgs", abi.tupleEncoder(argumentTypes, funType.parameterTypes(), encodeForLibraryCall));
templ("argumentString", joinHumanReadablePrefixed(argumentStrings)); 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(!isDelegateCall || !funType.valueSet(), "Value set for delegatecall");
solAssert(!useStaticCall || !funType.valueSet(), "Value set for staticcall"); solAssert(!useStaticCall || !funType.valueSet(), "Value set for staticcall");
templ("hasValue", !isDelegateCall && !useStaticCall); templ("hasValue", !isDelegateCall && !useStaticCall);
templ("value", funType.valueSet() ? IRVariable(_functionCall.expression()).part("value").name() : "0"); 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()) if (funType.gasSet())
templ("gas", IRVariable(_functionCall.expression()).part("gas").name()); templ("gas", IRVariable(_functionCall.expression()).part("gas").name());
else if (m_context.evmVersion().canOverchargeGasForCall()) else if (m_context.evmVersion().canOverchargeGasForCall())
@ -1919,8 +1875,6 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
u256 gasNeededByCaller = evmasm::GasCosts::callGas(m_context.evmVersion()) + 10; u256 gasNeededByCaller = evmasm::GasCosts::callGas(m_context.evmVersion()) + 10;
if (funType.valueSet()) if (funType.valueSet())
gasNeededByCaller += evmasm::GasCosts::callValueTransferGas; gasNeededByCaller += evmasm::GasCosts::callValueTransferGas;
if (!checkExistence)
gasNeededByCaller += evmasm::GasCosts::callNewAccountGas; // we never know
templ("gas", "sub(gas(), " + formatNumber(gasNeededByCaller) + ")"); templ("gas", "sub(gas(), " + formatNumber(gasNeededByCaller) + ")");
} }
// Order is important here, STATICCALL might overlap with DELEGATECALL. // Order is important here, STATICCALL might overlap with DELEGATECALL.
@ -1933,8 +1887,103 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
templ("forwardingRevert", m_utils.forwardingRevertFunction()); templ("forwardingRevert", m_utils.forwardingRevertFunction());
solUnimplementedAssert(funKind != FunctionType::Kind::RIPEMD160, ""); m_code << templ.render();
solUnimplementedAssert(funKind != FunctionType::Kind::ECRecover, ""); }
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(); m_code << templ.render();
} }

View File

@ -106,6 +106,13 @@ private:
std::vector<ASTPointer<Expression const>> const& _arguments 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 /// @returns code that evaluates to the first unused memory slot (which does not have to
/// be empty). /// be empty).
static std::string freeMemory(); static std::string freeMemory();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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