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:
parent
60a21c6487
commit
e5514becb8
@ -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.
|
||||
|
@ -72,7 +72,7 @@ Json::Value gasToJson(GasEstimator::GasConsumption const& _gas)
|
||||
|
||||
Json::Value estimateGas(CompilerStack const& _compiler, string const& _contract)
|
||||
{
|
||||
eth::EVMSchedule schedule;// TODO: make relevant to supposed context.
|
||||
eth::EVMSchedule schedule;
|
||||
Json::Value gasEstimates(Json::objectValue);
|
||||
using Gas = GasEstimator::GasConsumption;
|
||||
if (!_compiler.assemblyItems(_contract) && !_compiler.runtimeAssemblyItems(_contract))
|
||||
|
@ -59,7 +59,7 @@ public:
|
||||
|
||||
void testCreationTimeGas(string const& _sourceCode)
|
||||
{
|
||||
EVMSchedule schedule;// TODO: make relevant to supposed context.
|
||||
EVMSchedule schedule;
|
||||
|
||||
compileAndRun(_sourceCode);
|
||||
auto state = make_shared<KnownState>();
|
||||
|
@ -2890,6 +2890,62 @@ BOOST_AUTO_TEST_CASE(generic_callcode)
|
||||
BOOST_CHECK_EQUAL(m_state.balance(c_senderAddress), 50);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(generic_delegatecall)
|
||||
{
|
||||
char const* sourceCode = R"**(
|
||||
contract receiver {
|
||||
uint public received;
|
||||
address public sender;
|
||||
uint public value;
|
||||
function receive(uint256 x) { received = x; sender = msg.sender; value = msg.value; }
|
||||
}
|
||||
contract sender {
|
||||
uint public received;
|
||||
address public sender;
|
||||
uint public value;
|
||||
function doSend(address rec)
|
||||
{
|
||||
bytes4 signature = bytes4(bytes32(sha3("receive(uint256)")));
|
||||
rec.delegatecall(signature, 23);
|
||||
}
|
||||
}
|
||||
)**";
|
||||
compileAndRun(sourceCode, 0, "receiver");
|
||||
u160 const c_receiverAddress = m_contractAddress;
|
||||
compileAndRun(sourceCode, 50, "sender");
|
||||
u160 const c_senderAddress = m_contractAddress;
|
||||
BOOST_CHECK(m_sender != c_senderAddress); // just for sanity
|
||||
BOOST_CHECK(callContractFunctionWithValue("doSend(address)", 11, c_receiverAddress) == encodeArgs());
|
||||
BOOST_CHECK(callContractFunction("received()") == encodeArgs(u256(23)));
|
||||
BOOST_CHECK(callContractFunction("sender()") == encodeArgs(u160(m_sender)));
|
||||
BOOST_CHECK(callContractFunction("value()") == encodeArgs(u256(11)));
|
||||
m_contractAddress = c_receiverAddress;
|
||||
BOOST_CHECK(callContractFunction("received()") == encodeArgs(u256(0)));
|
||||
BOOST_CHECK(callContractFunction("sender()") == encodeArgs(u256(0)));
|
||||
BOOST_CHECK(callContractFunction("value()") == encodeArgs(u256(0)));
|
||||
BOOST_CHECK(m_state.storage(c_receiverAddress).empty());
|
||||
BOOST_CHECK(!m_state.storage(c_senderAddress).empty());
|
||||
BOOST_CHECK_EQUAL(m_state.balance(c_receiverAddress), 0);
|
||||
BOOST_CHECK_EQUAL(m_state.balance(c_senderAddress), 50 + 11);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(library_call_in_homestead)
|
||||
{
|
||||
char const* sourceCode = R"(
|
||||
library Lib { function m() returns (address) { return msg.sender; } }
|
||||
contract Test {
|
||||
address public sender;
|
||||
function f() {
|
||||
sender = Lib.m();
|
||||
}
|
||||
}
|
||||
)";
|
||||
compileAndRun(sourceCode, 0, "Lib");
|
||||
compileAndRun(sourceCode, 0, "Test", bytes(), map<string, Address>{{"Lib", m_contractAddress}});
|
||||
BOOST_CHECK(callContractFunction("f()") == encodeArgs());
|
||||
BOOST_CHECK(callContractFunction("sender()") == encodeArgs(u160(m_sender)));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(store_bytes)
|
||||
{
|
||||
// this test just checks that the copy loop does not mess up the stack
|
||||
|
@ -3232,6 +3232,19 @@ BOOST_AUTO_TEST_CASE(int10abc_is_identifier)
|
||||
BOOST_CHECK(success(text));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(library_functions_do_not_have_value)
|
||||
{
|
||||
char const* text = R"(
|
||||
library L { function l() {} }
|
||||
contract test {
|
||||
function f() {
|
||||
L.l.value;
|
||||
}
|
||||
}
|
||||
)";
|
||||
BOOST_CHECK(!success(text));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user