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)},
|
{"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)
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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.
|
||||||
|
@ -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,
|
||||||
|
@ -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;
|
||||||
|
@ -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.
|
||||||
|
@ -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))
|
||||||
|
@ -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>();
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user