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 YulUtilFunctions::suffixedVariableNameList(string const& _baseName, size_t _startSuffix, size_t _endSuffix)
|
||||||
{
|
{
|
||||||
string result;
|
string result;
|
||||||
|
@ -161,6 +161,10 @@ public:
|
|||||||
|
|
||||||
std::string packedHashFunction(std::vector<Type const*> const& _givenTypes, std::vector<Type const*> const& _targetTypes);
|
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
|
/// @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,
|
/// 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.
|
/// 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);
|
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)
|
string IRGenerationContext::internalDispatch(size_t _in, size_t _out)
|
||||||
{
|
{
|
||||||
// TODO can we limit the generated functions to only those visited
|
// 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
|
/// @returns the variable (or comma-separated list of variables) that contain
|
||||||
/// the value of the given expression.
|
/// the value of the given expression.
|
||||||
std::string variable(Expression const& _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);
|
std::string internalDispatch(size_t _in, size_t _out);
|
||||||
|
|
||||||
|
@ -28,6 +28,8 @@
|
|||||||
#include <libsolidity/codegen/CompilerUtils.h>
|
#include <libsolidity/codegen/CompilerUtils.h>
|
||||||
#include <libsolidity/ast/TypeProvider.h>
|
#include <libsolidity/ast/TypeProvider.h>
|
||||||
|
|
||||||
|
#include <libevmasm/GasMeter.h>
|
||||||
|
|
||||||
#include <libyul/AsmPrinter.h>
|
#include <libyul/AsmPrinter.h>
|
||||||
#include <libyul/AsmData.h>
|
#include <libyul/AsmData.h>
|
||||||
#include <libyul/optimiser/ASTCopier.h>
|
#include <libyul/optimiser/ASTCopier.h>
|
||||||
@ -392,6 +394,15 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
|
|||||||
")\n";
|
")\n";
|
||||||
break;
|
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:
|
case FunctionType::Kind::Event:
|
||||||
{
|
{
|
||||||
auto const& event = dynamic_cast<EventDefinition const&>(functionType->declaration());
|
auto const& event = dynamic_cast<EventDefinition const&>(functionType->declaration());
|
||||||
@ -502,9 +513,12 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
|
|||||||
identifier = FunctionType(*function).externalIdentifier();
|
identifier = FunctionType(*function).externalIdentifier();
|
||||||
else
|
else
|
||||||
solAssert(false, "Contract member is neither variable nor function.");
|
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....
|
defineExpressionPart(_memberAccess, 1) << expressionAsType(
|
||||||
solUnimplementedAssert(false, "");
|
_memberAccess.expression(),
|
||||||
|
type.isPayable() ? *TypeProvider::payableAddress() : *TypeProvider::address()
|
||||||
|
) << "\n";
|
||||||
|
defineExpressionPart(_memberAccess, 2) << formatNumber(identifier) << "\n";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
solAssert(false, "Invalid member access in contract");
|
solAssert(false, "Invalid member access in contract");
|
||||||
@ -763,6 +777,181 @@ bool IRGeneratorForStatements::visit(Literal const& _literal)
|
|||||||
return false;
|
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)
|
string IRGeneratorForStatements::expressionAsType(Expression const& _expression, Type const& _to)
|
||||||
{
|
{
|
||||||
Type const& from = type(_expression);
|
Type const& from = type(_expression);
|
||||||
@ -787,6 +976,11 @@ ostream& IRGeneratorForStatements::defineExpression(Expression const& _expressio
|
|||||||
return m_code << "let " << m_context.variable(_expression) << " := ";
|
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)
|
void IRGeneratorForStatements::appendAndOrOperatorCode(BinaryOperation const& _binOp)
|
||||||
{
|
{
|
||||||
langutil::Token const op = _binOp.getOperator();
|
langutil::Token const op = _binOp.getOperator();
|
||||||
|
@ -64,10 +64,22 @@ public:
|
|||||||
bool visit(Literal const& _literal) override;
|
bool visit(Literal const& _literal) override;
|
||||||
|
|
||||||
private:
|
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,
|
/// @returns a Yul expression representing the current value of @a _expression,
|
||||||
/// converted to type @a _to if it does not yet have that type.
|
/// converted to type @a _to if it does not yet have that type.
|
||||||
std::string expressionAsType(Expression const& _expression, Type const& _to);
|
std::string expressionAsType(Expression const& _expression, Type const& _to);
|
||||||
std::ostream& defineExpression(Expression const& _expression);
|
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);
|
void appendAndOrOperatorCode(BinaryOperation const& _binOp);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user