mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #6677 from ethereum/externalCalls
[SolYul] External function calls
This commit is contained in:
commit
4de75b24c5
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user