mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
BREAKING: Implement delegatecall and make default for library calls.
This commit is contained in:
+24
-23
@@ -333,6 +333,7 @@ MemberList::MemberMap IntegerType::nativeMembers(ContractDefinition const*) cons
|
||||
{"balance", make_shared<IntegerType >(256)},
|
||||
{"call", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Location::Bare, true)},
|
||||
{"callcode", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Location::BareCallCode, true)},
|
||||
{"delegatecall", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Location::BareDelegateCall, true)},
|
||||
{"send", make_shared<FunctionType>(strings{"uint"}, strings{"bool"}, FunctionType::Location::Send)}
|
||||
};
|
||||
else
|
||||
@@ -1561,9 +1562,9 @@ unsigned FunctionType::sizeOnStack() const
|
||||
}
|
||||
|
||||
unsigned size = 0;
|
||||
if (location == Location::External || location == Location::CallCode)
|
||||
if (location == Location::External || location == Location::CallCode || location == Location::DelegateCall)
|
||||
size = 2;
|
||||
else if (location == Location::Bare || location == Location::BareCallCode)
|
||||
else if (location == Location::Bare || location == Location::BareCallCode || location == Location::BareDelegateCall)
|
||||
size = 1;
|
||||
else if (location == Location::Internal)
|
||||
size = 1;
|
||||
@@ -1619,9 +1620,11 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con
|
||||
case Location::RIPEMD160:
|
||||
case Location::Bare:
|
||||
case Location::BareCallCode:
|
||||
case Location::BareDelegateCall:
|
||||
{
|
||||
MemberList::MemberMap members{
|
||||
{
|
||||
MemberList::MemberMap members;
|
||||
if (m_location != Location::BareDelegateCall)
|
||||
members.push_back(MemberList::Member(
|
||||
"value",
|
||||
make_shared<FunctionType>(
|
||||
parseElementaryTypeVector({"uint"}),
|
||||
@@ -1634,25 +1637,22 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con
|
||||
m_gasSet,
|
||||
m_valueSet
|
||||
)
|
||||
}
|
||||
};
|
||||
));
|
||||
if (m_location != Location::Creation)
|
||||
members.push_back(
|
||||
MemberList::Member(
|
||||
"gas",
|
||||
make_shared<FunctionType>(
|
||||
parseElementaryTypeVector({"uint"}),
|
||||
TypePointers{copyAndSetGasOrValue(true, false)},
|
||||
strings(),
|
||||
strings(),
|
||||
Location::SetGas,
|
||||
false,
|
||||
nullptr,
|
||||
m_gasSet,
|
||||
m_valueSet
|
||||
)
|
||||
members.push_back(MemberList::Member(
|
||||
"gas",
|
||||
make_shared<FunctionType>(
|
||||
parseElementaryTypeVector({"uint"}),
|
||||
TypePointers{copyAndSetGasOrValue(true, false)},
|
||||
strings(),
|
||||
strings(),
|
||||
Location::SetGas,
|
||||
false,
|
||||
nullptr,
|
||||
m_gasSet,
|
||||
m_valueSet
|
||||
)
|
||||
);
|
||||
));
|
||||
return members;
|
||||
}
|
||||
default:
|
||||
@@ -1700,6 +1700,7 @@ bool FunctionType::isBareCall() const
|
||||
{
|
||||
case Location::Bare:
|
||||
case Location::BareCallCode:
|
||||
case Location::BareDelegateCall:
|
||||
case Location::ECRecover:
|
||||
case Location::SHA256:
|
||||
case Location::RIPEMD160:
|
||||
@@ -1785,7 +1786,7 @@ FunctionTypePointer FunctionType::asMemberFunction(bool _inLibrary, bool _bound)
|
||||
returnParameterTypes,
|
||||
m_parameterNames,
|
||||
returnParameterNames,
|
||||
_inLibrary ? Location::CallCode : m_location,
|
||||
_inLibrary ? Location::DelegateCall : m_location,
|
||||
m_arbitraryParameters,
|
||||
m_declaration,
|
||||
m_gasSet,
|
||||
@@ -1884,7 +1885,7 @@ MemberList::MemberMap TypeType::nativeMembers(ContractDefinition const* _current
|
||||
for (auto const& it: contract.interfaceFunctions())
|
||||
members.push_back(MemberList::Member(
|
||||
it.second->declaration().name(),
|
||||
it.second->asMemberFunction(true), // use callcode
|
||||
it.second->asMemberFunction(true),
|
||||
&it.second->declaration()
|
||||
));
|
||||
if (isBase)
|
||||
|
||||
@@ -732,8 +732,10 @@ public:
|
||||
Internal, ///< stack-call using plain JUMP
|
||||
External, ///< external call using CALL
|
||||
CallCode, ///< extercnal call using CALLCODE, i.e. not exchanging the storage
|
||||
DelegateCall, ///< extercnal call using DELEGATECALL, i.e. not exchanging the storage
|
||||
Bare, ///< CALL without function hash
|
||||
BareCallCode, ///< CALLCODE without function hash
|
||||
BareDelegateCall, ///< DELEGATECALL without function hash
|
||||
Creation, ///< external call using CREATE
|
||||
Send, ///< CALL, but without data and gas
|
||||
SHA3, ///< SHA3
|
||||
@@ -869,7 +871,7 @@ public:
|
||||
/// removed and the location of reference types is changed from CallData to Memory.
|
||||
/// This is needed if external functions are called on other contracts, as they cannot return
|
||||
/// dynamic values.
|
||||
/// @param _inLibrary if true, uses CallCode as location.
|
||||
/// @param _inLibrary if true, uses DelegateCall as location.
|
||||
/// @param _bound if true, the argumenst are placed as `arg1.functionName(arg2, ..., argn)`.
|
||||
FunctionTypePointer asMemberFunction(bool _inLibrary, bool _bound = false) const;
|
||||
|
||||
|
||||
@@ -773,15 +773,13 @@ eth::Assembly Compiler::cloneRuntime()
|
||||
a << u256(0) << eth::Instruction::DUP1 << eth::Instruction::CALLDATACOPY;
|
||||
//@todo adjust for larger return values, make this dynamic.
|
||||
a << u256(0x20) << u256(0) << eth::Instruction::CALLDATASIZE;
|
||||
// unfortunately, we have to send the value again, so that CALLVALUE returns the correct value
|
||||
// in the callcoded contract.
|
||||
a << u256(0) << eth::Instruction::CALLVALUE;
|
||||
a << u256(0);
|
||||
// this is the address which has to be substituted by the linker.
|
||||
//@todo implement as special "marker" AssemblyItem.
|
||||
a << u256("0xcafecafecafecafecafecafecafecafecafecafe");
|
||||
a << u256(schedule.callGas + schedule.callValueTransferGas + 10) << eth::Instruction::GAS << eth::Instruction::SUB;
|
||||
a << eth::Instruction::CALLCODE;
|
||||
//Propagate error condition (if CALLCODE pushes 0 on stack).
|
||||
a << u256(schedule.callGas + 10) << eth::Instruction::GAS << eth::Instruction::SUB;
|
||||
a << eth::Instruction::DELEGATECALL;
|
||||
//Propagate error condition (if DELEGATECALL pushes 0 on stack).
|
||||
a << eth::Instruction::ISZERO;
|
||||
a.appendJumpI(a.errorTag());
|
||||
//@todo adjust for larger return values, make this dynamic.
|
||||
|
||||
@@ -44,7 +44,7 @@ public:
|
||||
ContractDefinition const& _contract,
|
||||
std::map<ContractDefinition const*, eth::Assembly const*> const& _contracts
|
||||
);
|
||||
/// Compiles a contract that uses CALLCODE to call into a pre-deployed version of the given
|
||||
/// Compiles a contract that uses DELEGATECALL to call into a pre-deployed version of the given
|
||||
/// contract at runtime, but contains the full creation-time code.
|
||||
void compileClone(
|
||||
ContractDefinition const& _contract,
|
||||
|
||||
@@ -465,8 +465,8 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
||||
{
|
||||
FunctionType const& function = *functionType;
|
||||
if (function.bound())
|
||||
// Only callcode functions can be bound, this might be lifted later.
|
||||
solAssert(function.location() == Location::CallCode, "");
|
||||
// Only delegatecall functions can be bound, this might be lifted later.
|
||||
solAssert(function.location() == Location::DelegateCall, "");
|
||||
switch (function.location())
|
||||
{
|
||||
case Location::Internal:
|
||||
@@ -492,8 +492,10 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
||||
}
|
||||
case Location::External:
|
||||
case Location::CallCode:
|
||||
case Location::DelegateCall:
|
||||
case Location::Bare:
|
||||
case Location::BareCallCode:
|
||||
case Location::BareDelegateCall:
|
||||
_functionCall.expression().accept(*this);
|
||||
appendExternalFunctionCall(function, arguments);
|
||||
break;
|
||||
@@ -875,7 +877,7 @@ void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess)
|
||||
);
|
||||
m_context << eth::Instruction::BALANCE;
|
||||
}
|
||||
else if ((set<string>{"send", "call", "callcode"}).count(member))
|
||||
else if ((set<string>{"send", "call", "callcode", "delegatecall"}).count(member))
|
||||
utils().convertType(
|
||||
*_memberAccess.expression().annotation().type,
|
||||
IntegerType(0, IntegerType::Modifier::Address),
|
||||
@@ -1356,6 +1358,7 @@ void ExpressionCompiler::appendExternalFunctionCall(
|
||||
FunctionKind funKind = _functionType.location();
|
||||
bool returnSuccessCondition = funKind == FunctionKind::Bare || funKind == FunctionKind::BareCallCode;
|
||||
bool isCallCode = funKind == FunctionKind::BareCallCode || funKind == FunctionKind::CallCode;
|
||||
bool isDelegateCall = funKind == FunctionKind::BareDelegateCall || funKind == FunctionKind::DelegateCall;
|
||||
|
||||
unsigned retSize = 0;
|
||||
if (returnSuccessCondition)
|
||||
@@ -1371,13 +1374,13 @@ void ExpressionCompiler::appendExternalFunctionCall(
|
||||
TypePointers argumentTypes;
|
||||
TypePointers parameterTypes = _functionType.parameterTypes();
|
||||
bool manualFunctionId =
|
||||
(funKind == FunctionKind::Bare || funKind == FunctionKind::BareCallCode) &&
|
||||
(funKind == FunctionKind::Bare || funKind == FunctionKind::BareCallCode || funKind == FunctionKind::BareDelegateCall) &&
|
||||
!_arguments.empty() &&
|
||||
_arguments.front()->annotation().type->mobileType()->calldataEncodedSize(false) ==
|
||||
CompilerUtils::dataStartOffset;
|
||||
if (manualFunctionId)
|
||||
{
|
||||
// If we have a BareCall or BareCallCode and the first type has exactly 4 bytes, use it as
|
||||
// If we have a Bare* and the first type has exactly 4 bytes, use it as
|
||||
// function identifier.
|
||||
_arguments.front()->accept(*this);
|
||||
utils().convertType(
|
||||
@@ -1416,7 +1419,7 @@ void ExpressionCompiler::appendExternalFunctionCall(
|
||||
parameterTypes,
|
||||
_functionType.padArguments(),
|
||||
_functionType.takesArbitraryParameters(),
|
||||
isCallCode
|
||||
isCallCode || isDelegateCall
|
||||
);
|
||||
|
||||
// Stack now:
|
||||
@@ -1435,8 +1438,10 @@ void ExpressionCompiler::appendExternalFunctionCall(
|
||||
m_context << eth::Instruction::DUP2;
|
||||
|
||||
// CALL arguments: outSize, outOff, inSize, inOff (already present up to here)
|
||||
// value, addr, gas (stack top)
|
||||
if (_functionType.valueSet())
|
||||
// [value,] addr, gas (stack top)
|
||||
if (isDelegateCall)
|
||||
solAssert(!_functionType.valueSet(), "Value set for delegatecall");
|
||||
else if (_functionType.valueSet())
|
||||
m_context << eth::dupInstruction(m_context.baseToCurrentStackOffset(valueStackPos));
|
||||
else
|
||||
m_context << u256(0);
|
||||
@@ -1446,20 +1451,22 @@ void ExpressionCompiler::appendExternalFunctionCall(
|
||||
m_context << eth::dupInstruction(m_context.baseToCurrentStackOffset(gasStackPos));
|
||||
else
|
||||
{
|
||||
eth::EVMSchedule schedule;// TODO: Make relevant to current suppose context.
|
||||
eth::EVMSchedule schedule;
|
||||
// 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 = schedule.callGas + 10;
|
||||
if (_functionType.valueSet())
|
||||
gasNeededByCaller += schedule.callValueTransferGas;
|
||||
if (!isCallCode)
|
||||
if (!isCallCode && !isDelegateCall)
|
||||
gasNeededByCaller += schedule.callNewAccountGas; // we never know
|
||||
m_context <<
|
||||
gasNeededByCaller <<
|
||||
eth::Instruction::GAS <<
|
||||
eth::Instruction::SUB;
|
||||
}
|
||||
if (isCallCode)
|
||||
if (isDelegateCall)
|
||||
m_context << eth::Instruction::DELEGATECALL;
|
||||
else if (isCallCode)
|
||||
m_context << eth::Instruction::CALLCODE;
|
||||
else
|
||||
m_context << eth::Instruction::CALL;
|
||||
|
||||
@@ -124,7 +124,7 @@ public:
|
||||
eth::LinkerObject const& object(std::string const& _contractName = "") const;
|
||||
/// @returns the runtime object for the contract.
|
||||
eth::LinkerObject const& runtimeObject(std::string const& _contractName = "") const;
|
||||
/// @returns the bytecode of a contract that uses an already deployed contract via CALLCODE.
|
||||
/// @returns the bytecode of a contract that uses an already deployed contract via DELEGATECALL.
|
||||
/// The returned bytes will contain a sequence of 20 bytes of the format "XXX...XXX" which have to
|
||||
/// substituted by the actual address. Note that this sequence starts end ends in three X
|
||||
/// characters but can contain anything in between.
|
||||
|
||||
Reference in New Issue
Block a user