BREAKING: Implement delegatecall and make default for library calls.

This commit is contained in:
chriseth 2016-03-07 16:55:53 +01:00
parent 60a21c6487
commit e5514becb8
10 changed files with 122 additions and 45 deletions

View File

@ -333,6 +333,7 @@ MemberList::MemberMap IntegerType::nativeMembers(ContractDefinition const*) cons
{"balance", make_shared<IntegerType >(256)}, {"balance", make_shared<IntegerType >(256)},
{"call", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Location::Bare, true)}, {"call", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Location::Bare, true)},
{"callcode", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Location::BareCallCode, 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)} {"send", make_shared<FunctionType>(strings{"uint"}, strings{"bool"}, FunctionType::Location::Send)}
}; };
else else
@ -1561,9 +1562,9 @@ unsigned FunctionType::sizeOnStack() const
} }
unsigned size = 0; unsigned size = 0;
if (location == Location::External || location == Location::CallCode) if (location == Location::External || location == Location::CallCode || location == Location::DelegateCall)
size = 2; size = 2;
else if (location == Location::Bare || location == Location::BareCallCode) else if (location == Location::Bare || location == Location::BareCallCode || location == Location::BareDelegateCall)
size = 1; size = 1;
else if (location == Location::Internal) else if (location == Location::Internal)
size = 1; size = 1;
@ -1619,9 +1620,11 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con
case Location::RIPEMD160: case Location::RIPEMD160:
case Location::Bare: case Location::Bare:
case Location::BareCallCode: case Location::BareCallCode:
case Location::BareDelegateCall:
{ {
MemberList::MemberMap members{ MemberList::MemberMap members;
{ if (m_location != Location::BareDelegateCall)
members.push_back(MemberList::Member(
"value", "value",
make_shared<FunctionType>( make_shared<FunctionType>(
parseElementaryTypeVector({"uint"}), parseElementaryTypeVector({"uint"}),
@ -1634,11 +1637,9 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con
m_gasSet, m_gasSet,
m_valueSet m_valueSet
) )
} ));
};
if (m_location != Location::Creation) if (m_location != Location::Creation)
members.push_back( members.push_back(MemberList::Member(
MemberList::Member(
"gas", "gas",
make_shared<FunctionType>( make_shared<FunctionType>(
parseElementaryTypeVector({"uint"}), parseElementaryTypeVector({"uint"}),
@ -1651,8 +1652,7 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con
m_gasSet, m_gasSet,
m_valueSet m_valueSet
) )
) ));
);
return members; return members;
} }
default: default:
@ -1700,6 +1700,7 @@ bool FunctionType::isBareCall() const
{ {
case Location::Bare: case Location::Bare:
case Location::BareCallCode: case Location::BareCallCode:
case Location::BareDelegateCall:
case Location::ECRecover: case Location::ECRecover:
case Location::SHA256: case Location::SHA256:
case Location::RIPEMD160: case Location::RIPEMD160:
@ -1785,7 +1786,7 @@ FunctionTypePointer FunctionType::asMemberFunction(bool _inLibrary, bool _bound)
returnParameterTypes, returnParameterTypes,
m_parameterNames, m_parameterNames,
returnParameterNames, returnParameterNames,
_inLibrary ? Location::CallCode : m_location, _inLibrary ? Location::DelegateCall : m_location,
m_arbitraryParameters, m_arbitraryParameters,
m_declaration, m_declaration,
m_gasSet, m_gasSet,
@ -1884,7 +1885,7 @@ MemberList::MemberMap TypeType::nativeMembers(ContractDefinition const* _current
for (auto const& it: contract.interfaceFunctions()) for (auto const& it: contract.interfaceFunctions())
members.push_back(MemberList::Member( members.push_back(MemberList::Member(
it.second->declaration().name(), it.second->declaration().name(),
it.second->asMemberFunction(true), // use callcode it.second->asMemberFunction(true),
&it.second->declaration() &it.second->declaration()
)); ));
if (isBase) if (isBase)

View File

@ -732,8 +732,10 @@ public:
Internal, ///< stack-call using plain JUMP Internal, ///< stack-call using plain JUMP
External, ///< external call using CALL External, ///< external call using CALL
CallCode, ///< extercnal call using CALLCODE, i.e. not exchanging the storage 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 Bare, ///< CALL without function hash
BareCallCode, ///< CALLCODE without function hash BareCallCode, ///< CALLCODE without function hash
BareDelegateCall, ///< DELEGATECALL without function hash
Creation, ///< external call using CREATE Creation, ///< external call using CREATE
Send, ///< CALL, but without data and gas Send, ///< CALL, but without data and gas
SHA3, ///< SHA3 SHA3, ///< SHA3
@ -869,7 +871,7 @@ public:
/// removed and the location of reference types is changed from CallData to Memory. /// 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 /// This is needed if external functions are called on other contracts, as they cannot return
/// dynamic values. /// 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)`. /// @param _bound if true, the argumenst are placed as `arg1.functionName(arg2, ..., argn)`.
FunctionTypePointer asMemberFunction(bool _inLibrary, bool _bound = false) const; FunctionTypePointer asMemberFunction(bool _inLibrary, bool _bound = false) const;

View File

@ -773,15 +773,13 @@ eth::Assembly Compiler::cloneRuntime()
a << u256(0) << eth::Instruction::DUP1 << eth::Instruction::CALLDATACOPY; a << u256(0) << eth::Instruction::DUP1 << eth::Instruction::CALLDATACOPY;
//@todo adjust for larger return values, make this dynamic. //@todo adjust for larger return values, make this dynamic.
a << u256(0x20) << u256(0) << eth::Instruction::CALLDATASIZE; a << u256(0x20) << u256(0) << eth::Instruction::CALLDATASIZE;
// unfortunately, we have to send the value again, so that CALLVALUE returns the correct value a << u256(0);
// in the callcoded contract.
a << u256(0) << eth::Instruction::CALLVALUE;
// this is the address which has to be substituted by the linker. // this is the address which has to be substituted by the linker.
//@todo implement as special "marker" AssemblyItem. //@todo implement as special "marker" AssemblyItem.
a << u256("0xcafecafecafecafecafecafecafecafecafecafe"); a << u256("0xcafecafecafecafecafecafecafecafecafecafe");
a << u256(schedule.callGas + schedule.callValueTransferGas + 10) << eth::Instruction::GAS << eth::Instruction::SUB; a << u256(schedule.callGas + 10) << eth::Instruction::GAS << eth::Instruction::SUB;
a << eth::Instruction::CALLCODE; a << eth::Instruction::DELEGATECALL;
//Propagate error condition (if CALLCODE pushes 0 on stack). //Propagate error condition (if DELEGATECALL pushes 0 on stack).
a << eth::Instruction::ISZERO; a << eth::Instruction::ISZERO;
a.appendJumpI(a.errorTag()); a.appendJumpI(a.errorTag());
//@todo adjust for larger return values, make this dynamic. //@todo adjust for larger return values, make this dynamic.

View File

@ -44,7 +44,7 @@ public:
ContractDefinition const& _contract, ContractDefinition const& _contract,
std::map<ContractDefinition const*, eth::Assembly const*> const& _contracts 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. /// contract at runtime, but contains the full creation-time code.
void compileClone( void compileClone(
ContractDefinition const& _contract, ContractDefinition const& _contract,

View File

@ -465,8 +465,8 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
{ {
FunctionType const& function = *functionType; FunctionType const& function = *functionType;
if (function.bound()) if (function.bound())
// Only callcode functions can be bound, this might be lifted later. // Only delegatecall functions can be bound, this might be lifted later.
solAssert(function.location() == Location::CallCode, ""); solAssert(function.location() == Location::DelegateCall, "");
switch (function.location()) switch (function.location())
{ {
case Location::Internal: case Location::Internal:
@ -492,8 +492,10 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
} }
case Location::External: case Location::External:
case Location::CallCode: case Location::CallCode:
case Location::DelegateCall:
case Location::Bare: case Location::Bare:
case Location::BareCallCode: case Location::BareCallCode:
case Location::BareDelegateCall:
_functionCall.expression().accept(*this); _functionCall.expression().accept(*this);
appendExternalFunctionCall(function, arguments); appendExternalFunctionCall(function, arguments);
break; break;
@ -875,7 +877,7 @@ void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess)
); );
m_context << eth::Instruction::BALANCE; 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( utils().convertType(
*_memberAccess.expression().annotation().type, *_memberAccess.expression().annotation().type,
IntegerType(0, IntegerType::Modifier::Address), IntegerType(0, IntegerType::Modifier::Address),
@ -1356,6 +1358,7 @@ void ExpressionCompiler::appendExternalFunctionCall(
FunctionKind funKind = _functionType.location(); FunctionKind funKind = _functionType.location();
bool returnSuccessCondition = funKind == FunctionKind::Bare || funKind == FunctionKind::BareCallCode; bool returnSuccessCondition = funKind == FunctionKind::Bare || funKind == FunctionKind::BareCallCode;
bool isCallCode = funKind == FunctionKind::BareCallCode || funKind == FunctionKind::CallCode; bool isCallCode = funKind == FunctionKind::BareCallCode || funKind == FunctionKind::CallCode;
bool isDelegateCall = funKind == FunctionKind::BareDelegateCall || funKind == FunctionKind::DelegateCall;
unsigned retSize = 0; unsigned retSize = 0;
if (returnSuccessCondition) if (returnSuccessCondition)
@ -1371,13 +1374,13 @@ void ExpressionCompiler::appendExternalFunctionCall(
TypePointers argumentTypes; TypePointers argumentTypes;
TypePointers parameterTypes = _functionType.parameterTypes(); TypePointers parameterTypes = _functionType.parameterTypes();
bool manualFunctionId = bool manualFunctionId =
(funKind == FunctionKind::Bare || funKind == FunctionKind::BareCallCode) && (funKind == FunctionKind::Bare || funKind == FunctionKind::BareCallCode || funKind == FunctionKind::BareDelegateCall) &&
!_arguments.empty() && !_arguments.empty() &&
_arguments.front()->annotation().type->mobileType()->calldataEncodedSize(false) == _arguments.front()->annotation().type->mobileType()->calldataEncodedSize(false) ==
CompilerUtils::dataStartOffset; CompilerUtils::dataStartOffset;
if (manualFunctionId) 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. // function identifier.
_arguments.front()->accept(*this); _arguments.front()->accept(*this);
utils().convertType( utils().convertType(
@ -1416,7 +1419,7 @@ void ExpressionCompiler::appendExternalFunctionCall(
parameterTypes, parameterTypes,
_functionType.padArguments(), _functionType.padArguments(),
_functionType.takesArbitraryParameters(), _functionType.takesArbitraryParameters(),
isCallCode isCallCode || isDelegateCall
); );
// Stack now: // Stack now:
@ -1435,8 +1438,10 @@ void ExpressionCompiler::appendExternalFunctionCall(
m_context << eth::Instruction::DUP2; m_context << eth::Instruction::DUP2;
// CALL arguments: outSize, outOff, inSize, inOff (already present up to here) // CALL arguments: outSize, outOff, inSize, inOff (already present up to here)
// value, addr, gas (stack top) // [value,] addr, gas (stack top)
if (_functionType.valueSet()) if (isDelegateCall)
solAssert(!_functionType.valueSet(), "Value set for delegatecall");
else if (_functionType.valueSet())
m_context << eth::dupInstruction(m_context.baseToCurrentStackOffset(valueStackPos)); m_context << eth::dupInstruction(m_context.baseToCurrentStackOffset(valueStackPos));
else else
m_context << u256(0); m_context << u256(0);
@ -1446,20 +1451,22 @@ void ExpressionCompiler::appendExternalFunctionCall(
m_context << eth::dupInstruction(m_context.baseToCurrentStackOffset(gasStackPos)); m_context << eth::dupInstruction(m_context.baseToCurrentStackOffset(gasStackPos));
else 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" // 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. // @todo this retains too much gas for now, needs to be fine-tuned.
u256 gasNeededByCaller = schedule.callGas + 10; u256 gasNeededByCaller = schedule.callGas + 10;
if (_functionType.valueSet()) if (_functionType.valueSet())
gasNeededByCaller += schedule.callValueTransferGas; gasNeededByCaller += schedule.callValueTransferGas;
if (!isCallCode) if (!isCallCode && !isDelegateCall)
gasNeededByCaller += schedule.callNewAccountGas; // we never know gasNeededByCaller += schedule.callNewAccountGas; // we never know
m_context << m_context <<
gasNeededByCaller << gasNeededByCaller <<
eth::Instruction::GAS << eth::Instruction::GAS <<
eth::Instruction::SUB; eth::Instruction::SUB;
} }
if (isCallCode) if (isDelegateCall)
m_context << eth::Instruction::DELEGATECALL;
else if (isCallCode)
m_context << eth::Instruction::CALLCODE; m_context << eth::Instruction::CALLCODE;
else else
m_context << eth::Instruction::CALL; m_context << eth::Instruction::CALL;

View File

@ -124,7 +124,7 @@ public:
eth::LinkerObject const& object(std::string const& _contractName = "") const; eth::LinkerObject const& object(std::string const& _contractName = "") const;
/// @returns the runtime object for the contract. /// @returns the runtime object for the contract.
eth::LinkerObject const& runtimeObject(std::string const& _contractName = "") const; 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 /// 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 /// substituted by the actual address. Note that this sequence starts end ends in three X
/// characters but can contain anything in between. /// characters but can contain anything in between.

View File

@ -72,7 +72,7 @@ Json::Value gasToJson(GasEstimator::GasConsumption const& _gas)
Json::Value estimateGas(CompilerStack const& _compiler, string const& _contract) 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); Json::Value gasEstimates(Json::objectValue);
using Gas = GasEstimator::GasConsumption; using Gas = GasEstimator::GasConsumption;
if (!_compiler.assemblyItems(_contract) && !_compiler.runtimeAssemblyItems(_contract)) if (!_compiler.assemblyItems(_contract) && !_compiler.runtimeAssemblyItems(_contract))

View File

@ -59,7 +59,7 @@ public:
void testCreationTimeGas(string const& _sourceCode) void testCreationTimeGas(string const& _sourceCode)
{ {
EVMSchedule schedule;// TODO: make relevant to supposed context. EVMSchedule schedule;
compileAndRun(_sourceCode); compileAndRun(_sourceCode);
auto state = make_shared<KnownState>(); auto state = make_shared<KnownState>();

View File

@ -2890,6 +2890,62 @@ BOOST_AUTO_TEST_CASE(generic_callcode)
BOOST_CHECK_EQUAL(m_state.balance(c_senderAddress), 50); 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) BOOST_AUTO_TEST_CASE(store_bytes)
{ {
// this test just checks that the copy loop does not mess up the stack // this test just checks that the copy loop does not mess up the stack

View File

@ -3232,6 +3232,19 @@ BOOST_AUTO_TEST_CASE(int10abc_is_identifier)
BOOST_CHECK(success(text)); 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() BOOST_AUTO_TEST_SUITE_END()
} }