Merge pull request #6677 from ethereum/externalCalls

[SolYul] External function calls
This commit is contained in:
chriseth 2019-05-09 19:18:10 +02:00 committed by GitHub
commit 4de75b24c5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 249 additions and 3 deletions

View File

@ -1036,6 +1036,31 @@ string YulUtilFunctions::packedHashFunction(
});
}
string YulUtilFunctions::forwardingRevertFunction()
{
bool forward = m_evmVersion.supportsReturndata();
string functionName = "revert_forward_" + to_string(forward);
return m_functionCollector->createFunction(functionName, [&]() {
if (forward)
return Whiskers(R"(
function <functionName>() {
returndatacopy(0, 0, returndatasize())
revert(0, returndatasize())
}
)")
("functionName", functionName)
.render();
else
return Whiskers(R"(
function <functionName>() {
revert(0, 0)
}
)")
("functionName", functionName)
.render();
});
}
string YulUtilFunctions::suffixedVariableNameList(string const& _baseName, size_t _startSuffix, size_t _endSuffix)
{
string result;

View File

@ -161,6 +161,10 @@ public:
std::string packedHashFunction(std::vector<Type const*> const& _givenTypes, std::vector<Type const*> const& _targetTypes);
/// @returns the name of a function that reverts and uses returndata (if available)
/// as reason string.
std::string forwardingRevertFunction();
/// @returns a string containing a comma-separated list of variable names consisting of @a _baseName suffixed
/// with increasing integers in the range [@a _startSuffix, @a _endSuffix), if @a _startSuffix < @a _endSuffix,
/// and with decreasing integers in the range [@a _endSuffix, @a _startSuffix), if @a _endSuffix < @a _startSuffix.

View File

@ -101,6 +101,14 @@ string IRGenerationContext::variable(Expression const& _expression)
return YulUtilFunctions::suffixedVariableNameList(move(var) + "_", 1, 1 + size);
}
string IRGenerationContext::variablePart(Expression const& _expression, size_t _part)
{
size_t numVars = _expression.annotation().type->sizeOnStack();
solAssert(numVars > 1, "");
solAssert(1 <= _part && _part <= numVars, "");
return "expr_" + to_string(_expression.id()) + "_" + to_string(_part);
}
string IRGenerationContext::internalDispatch(size_t _in, size_t _out)
{
// TODO can we limit the generated functions to only those visited

View File

@ -83,6 +83,9 @@ public:
/// @returns the variable (or comma-separated list of variables) that contain
/// the value of the given expression.
std::string variable(Expression const& _expression);
/// @returns the variable of a multi-variable expression. Variables are numbered
/// starting from 1.
std::string variablePart(Expression const& _expression, size_t _part);
std::string internalDispatch(size_t _in, size_t _out);

View File

@ -28,6 +28,8 @@
#include <libsolidity/codegen/CompilerUtils.h>
#include <libsolidity/ast/TypeProvider.h>
#include <libevmasm/GasMeter.h>
#include <libyul/AsmPrinter.h>
#include <libyul/AsmData.h>
#include <libyul/optimiser/ASTCopier.h>
@ -392,6 +394,15 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
")\n";
break;
}
case FunctionType::Kind::External:
case FunctionType::Kind::DelegateCall:
case FunctionType::Kind::BareCall:
case FunctionType::Kind::BareDelegateCall:
case FunctionType::Kind::BareStaticCall:
appendExternalFunctionCall(_functionCall, arguments);
break;
case FunctionType::Kind::BareCallCode:
solAssert(false, "Callcode has been removed.");
case FunctionType::Kind::Event:
{
auto const& event = dynamic_cast<EventDefinition const&>(functionType->declaration());
@ -502,9 +513,12 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
identifier = FunctionType(*function).externalIdentifier();
else
solAssert(false, "Contract member is neither variable nor function.");
// TODO here, we need to assign address and function identifier to two variables.
// We migt also just combine them into a single variable already....
solUnimplementedAssert(false, "");
defineExpressionPart(_memberAccess, 1) << expressionAsType(
_memberAccess.expression(),
type.isPayable() ? *TypeProvider::payableAddress() : *TypeProvider::address()
) << "\n";
defineExpressionPart(_memberAccess, 2) << formatNumber(identifier) << "\n";
}
else
solAssert(false, "Invalid member access in contract");
@ -763,6 +777,181 @@ bool IRGeneratorForStatements::visit(Literal const& _literal)
return false;
}
void IRGeneratorForStatements::appendExternalFunctionCall(
FunctionCall const& _functionCall,
vector<ASTPointer<Expression const>> const& _arguments
)
{
FunctionType const& funType = dynamic_cast<FunctionType const&>(type(_functionCall.expression()));
solAssert(
funType.takesArbitraryParameters() ||
_arguments.size() == funType.parameterTypes().size(), ""
);
solUnimplementedAssert(!funType.bound(), "");
FunctionType::Kind funKind = funType.kind();
solAssert(funKind != FunctionType::Kind::BareStaticCall || m_context.evmVersion().hasStaticCall(), "");
solAssert(funKind != FunctionType::Kind::BareCallCode, "Callcode has been removed.");
bool returnSuccessConditionAndReturndata = funKind == FunctionType::Kind::BareCall || funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::BareStaticCall;
bool isDelegateCall = funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::DelegateCall;
bool useStaticCall = funKind == FunctionType::Kind::BareStaticCall || (funType.stateMutability() <= StateMutability::View && m_context.evmVersion().hasStaticCall());
bool haveReturndatacopy = m_context.evmVersion().supportsReturndata();
unsigned retSize = 0;
bool dynamicReturnSize = false;
TypePointers returnTypes;
if (!returnSuccessConditionAndReturndata)
{
if (haveReturndatacopy)
returnTypes = funType.returnParameterTypes();
else
returnTypes = funType.returnParameterTypesWithoutDynamicTypes();
for (auto const& retType: returnTypes)
if (retType->isDynamicallyEncoded())
{
solAssert(haveReturndatacopy, "");
dynamicReturnSize = true;
retSize = 0;
break;
}
else if (retType->decodingType())
retSize += retType->decodingType()->calldataEncodedSize();
else
retSize += retType->calldataEncodedSize();
}
TypePointers argumentTypes;
string argumentString;
for (auto const& arg: _arguments)
{
argumentTypes.emplace_back(&type(*arg));
string var = m_context.variable(*arg);
if (!var.empty())
argumentString += ", " + move(var);
}
solUnimplementedAssert(funKind != FunctionType::Kind::ECRecover, "");
if (!m_context.evmVersion().canOverchargeGasForCall())
{
// Touch the end of the output area so that we do not pay for memory resize during the call
// (which we would have to subtract from the gas left)
// We could also just use MLOAD; POP right before the gas calculation, but the optimizer
// would remove that, so we use MSTORE here.
if (!funType.gasSet() && retSize > 0)
m_code << "mstore(add(" << fetchFreeMem() << ", " << to_string(retSize) << "), 0)\n";
}
ABIFunctions abi(m_context.evmVersion(), m_context.functionCollector());
solUnimplementedAssert(!funType.isBareCall(), "");
Whiskers templ(R"(
<?checkExistence>
if iszero(extcodesize(<address>)) { revert(0, 0) }
</checkExistence>
let <pos> := <freeMem>
mstore(<pos>, <shl28>(<funId>))
let <end> := <encodeArgs>(add(<pos>, 4) <argumentString>)
let <result> := <call>(<gas>, <address>, <value>, <pos>, sub(<end>, <pos>), <pos>, <retSize>)
if iszero(<result>) { <forwardingRevert> }
<?dynamicReturnSize>
returndatacopy(<pos>, 0, returndatasize())
</dynamicReturnSize>
<allocate>
mstore(<freeMem>, add(<pos>, and(add(<retSize>, 0x1f), not(0x1f))))
<?returns> let <retvars> := </returns> <abiDecode>(<pos>, <retSize>)
)");
templ("pos", m_context.newYulVariable());
templ("end", m_context.newYulVariable());
templ("result", m_context.newYulVariable());
templ("freeMem", fetchFreeMem());
templ("shl28", m_utils.shiftLeftFunction(8 * (32 - 4)));
templ("funId", m_context.variablePart(_functionCall.expression(), 2));
// 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, "");
solUnimplementedAssert(!funType.padArguments(), "");
templ("encodeArgs", abi.tupleEncoder(argumentTypes, funType.parameterTypes(), encodeForLibraryCall));
templ("argumentString", argumentString);
// Output data will replace input data, unless we have ECRecover (then, output
// area will be 32 bytes just before input area).
templ("retSize", to_string(retSize));
solUnimplementedAssert(funKind != FunctionType::Kind::ECRecover, "");
if (isDelegateCall)
solAssert(!funType.valueSet(), "Value set for delegatecall");
else if (useStaticCall)
solAssert(!funType.valueSet(), "Value set for staticcall");
else if (funType.valueSet())
templ("value", m_context.variablePart(_functionCall.expression(), 4));
else
templ("value", "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", m_context.variablePart(_functionCall.expression(), 3));
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 = eth::GasCosts::callGas(m_context.evmVersion()) + 10;
if (funType.valueSet())
gasNeededByCaller += eth::GasCosts::callValueTransferGas;
if (!checkExistence)
gasNeededByCaller += eth::GasCosts::callNewAccountGas; // we never know
templ("gas", "sub(gas(), " + formatNumber(gasNeededByCaller) + ")");
}
// Order is important here, STATICCALL might overlap with DELEGATECALL.
if (isDelegateCall)
templ("call", "delegatecall");
else if (useStaticCall)
templ("call", "staticcall");
else
templ("call", "call");
templ("forwardingRevert", m_utils.forwardingRevertFunction());
solUnimplementedAssert(!returnSuccessConditionAndReturndata, "");
solUnimplementedAssert(funKind != FunctionType::Kind::RIPEMD160, "");
solUnimplementedAssert(funKind != FunctionType::Kind::ECRecover, "");
templ("dynamicReturnSize", dynamicReturnSize);
// 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.
if (haveReturndatacopy)
templ("returnSize", "returndatasize()");
else
templ("returnSize", to_string(retSize));
templ("abiDecode", abi.tupleDecoder(returnTypes, true));
templ("returns", !returnTypes.empty());
templ("retVars", m_context.variable(_functionCall));
}
string IRGeneratorForStatements::fetchFreeMem() const
{
return "mload(" + to_string(CompilerUtils::freeMemoryPointer) + ")";
}
string IRGeneratorForStatements::expressionAsType(Expression const& _expression, Type const& _to)
{
Type const& from = type(_expression);
@ -787,6 +976,11 @@ ostream& IRGeneratorForStatements::defineExpression(Expression const& _expressio
return m_code << "let " << m_context.variable(_expression) << " := ";
}
ostream& IRGeneratorForStatements::defineExpressionPart(Expression const& _expression, size_t _part)
{
return m_code << "let " << m_context.variablePart(_expression, _part) << " := ";
}
void IRGeneratorForStatements::appendAndOrOperatorCode(BinaryOperation const& _binOp)
{
langutil::Token const op = _binOp.getOperator();

View File

@ -64,10 +64,22 @@ public:
bool visit(Literal const& _literal) override;
private:
/// Appends code to call an external function with the given arguments.
/// All involved expressions have already been visited.
void appendExternalFunctionCall(
FunctionCall const& _functionCall,
std::vector<ASTPointer<Expression const>> const& _arguments
);
std::string fetchFreeMem() const;
/// @returns a Yul expression representing the current value of @a _expression,
/// converted to type @a _to if it does not yet have that type.
std::string expressionAsType(Expression const& _expression, Type const& _to);
std::ostream& defineExpression(Expression const& _expression);
/// Defines only one of many variables corresponding to an expression.
/// We start counting at 1 instead of 0.
std::ostream& defineExpressionPart(Expression const& _expression, size_t _part);
void appendAndOrOperatorCode(BinaryOperation const& _binOp);