Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 8 additions & 35 deletions docs/contracts.rst
Original file line number Diff line number Diff line change
Expand Up @@ -680,7 +680,7 @@ Such contracts cannot be compiled (even if they contain implemented functions al

If a contract inherits from an abstract contract and does not implement all non-implemented functions by overriding, it will itself be abstract.

.. index:: ! library, callcode
.. index:: ! library, callcode, delegatecall

.. _libraries:

Expand All @@ -689,7 +689,8 @@ Libraries
************

Libraries are similar to contracts, but their purpose is that they are deployed
only once at a specific address and their code is reused using the `CALLCODE`
only once at a specific address and their code is reused using the `DELEGATECALL`
(`CALLCODE` until homestead)
feature of the EVM. This means that if library functions are called, their code
is executed in the context of the calling contract, i.e. `this` points to the
calling contract and especially the storage from the calling contract can be
Expand Down Expand Up @@ -756,12 +757,12 @@ reference parameters, can have multiple storage reference
parameters and in any position.

The calls to `Set.contains`, `Set.insert` and `Set.remove`
are all compiled as calls (`CALLCODE`s) to an external
are all compiled as calls (`DELEGATECALL`s) to an external
contract/library. If you use libraries, take care that an
actual external function call is performed, so `msg.sender`
does not point to the original sender anymore but to the the
calling contract and also `msg.value` contains the funds
sent during the call to the library function.
actual external function call is performed.
`msg.sender`, `msg.value` and `this` will retain their values
in this call, though (prior to Homestead, `msg.sender` and
`msg.value` changed, though).

As the compiler cannot know where the library will be
deployed at, these addresses have to be filled into the
Expand All @@ -781,34 +782,6 @@ Restrictions for libraries in comparison to contracts:

(these might be lifted at a later point)

Common pitfalls for libraries
=============================

.. index:: msg;sender

The value of `msg.sender`
-------------------------

The value for `msg.sender` will be that of the contract which is calling the library function.

For example, if A calls contract B which internally calls library C, then within the function call of library C, `msg.sender` will be the address of contract B.

The reason for this is that the expression `LibraryName.functionName()`
performs an external function call using `CALLCODE`, which maps to a real EVM
call just like `otherContract.functionName()` or `this.functionName()`. This
call extends the call depth by one (limited to 1024), stores the caller (the
current contract) as `msg.sender`, and then executes the library contract's
code against the current contracts storage. This execution occurs in a
completely new memory context meaning that memory types will be copied and
cannot be passed by reference.

Transferring Ether
-------------------------

It is *in principle* possible to transfer ether using
`LibraryName.functionName.value(x)()`, but as `CALLCODE` is used, the Ether
will just end up at the current contract.

.. index:: ! using for, library

.. _using-for:
Expand Down
2 changes: 1 addition & 1 deletion docs/control-structures.rst
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ Assigning *to* a state variable always creates an independent copy. On the other
Exceptions
==========

There are some cases where exceptions are thrown automatically (see below). You can use the `throw` instruction to throw an exception manually. The effect of an exception is that the currently executing call is stopped and reverted (i.e. all changes to the state and balances are undone) and the exception is also "bubbled up" through Solidity function calls (exceptions are `send` and the low-level functions `call` and `callcode`, those return `false` in case of an exception).
There are some cases where exceptions are thrown automatically (see below). You can use the `throw` instruction to throw an exception manually. The effect of an exception is that the currently executing call is stopped and reverted (i.e. all changes to the state and balances are undone) and the exception is also "bubbled up" through Solidity function calls (exceptions are `send` and the low-level functions `call`, `delegatecall` and `callcode`, those return `false` in case of an exception).

Catching exceptions is not yet possible.

Expand Down
12 changes: 6 additions & 6 deletions docs/introduction-to-smart-contracts.rst
Original file line number Diff line number Diff line change
Expand Up @@ -406,15 +406,15 @@ a location in the caller's memory preallocated by the caller.
Calls are **limited** to a depth of 1024, which means that for more complex
operations, loops should be preferred over recursive calls.

.. index:: callcode, library
.. index:: delegatecall, callcode, library

Callcode and Libraries
======================
Delegatecall / Callcode and Libraries
=====================================

There exists a special variant of a message call, named **callcode**
There exists a special variant of a message call, named **delegatecall**
which is identical to a message call apart from the fact that
the code at the target address is executed in the context of the calling
contract.
contract and `msg.sender` and `msg.value` do not change their values.

This means that a contract can dynamically load code from a different
address at runtime. Storage, current address and balance still
Expand Down Expand Up @@ -461,4 +461,4 @@ The remaining Ether stored at that address is sent to a designated
target and then the storage and code is removed.

Note that even if a contract's code does not contain the `SELFDESTRUCT`
opcode, it can still perform that operation using callcode.
opcode, it can still perform that operation using delegatecall or callcode.
8 changes: 4 additions & 4 deletions docs/types.rst
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ Operators:
* Bit operators: `&`, `|`, `^` (bitwise exclusive or), `~` (bitwise negation)
* Arithmetic operators: `+`, `-`, unary `-`, unary `+`, `*`, `/`, `%` (remainder), `**` (exponentiation)

.. index:: address, balance, send, call, callcode
.. index:: address, balance, send, call, callcode, delegatecall

Address
-------
Expand Down Expand Up @@ -79,7 +79,7 @@ and to send Ether (in units of wei) to an address using the `send` function:
.. note::
If `x` is a contract address, its code (more specifically: its fallback function, if present) will be executed together with the `send` call (this is a limitation of the EVM and cannot be prevented). If that execution runs out of gas or fails in any way, the Ether transfer will be reverted. In this case, `send` returns `false`.

* `call` and `callcode`
* `call`, `callcode` and `delegatecall`

Furthermore, to interface with contracts that do not adhere to the ABI,
the function `call` is provided which takes an arbitrary number of arguments of any type. These arguments are padded to 32 bytes and concatenated. One exception is the case where the first argument is encoded to exactly four bytes. In this case, it is not padded to allow the use of function signatures here.
Expand All @@ -92,9 +92,9 @@ the function `call` is provided which takes an arbitrary number of arguments of

`call` returns a boolean indicating whether the invoked function terminated (`true`) or caused an EVM exception (`false`). It is not possible to access the actual data returned (for this we would need to know the encoding and size in advance).

In a similar way, the function `callcode` can be used: The difference is that only the code of the given address is used, all other aspects (storage, balance, ...) are taken from the current contract. The purpose of `callcode` is to use library code which is stored in another contract. The user has to ensure that the layout of storage in both contracts is suitable for callcode to be used.
In a similar way, the function `delegatecall` can be used: The difference is that only the code of the given address is used, all other aspects (storage, balance, ...) are taken from the current contract. The purpose of `delegatecall` is to use library code which is stored in another contract. The user has to ensure that the layout of storage in both contracts is suitable for delegatecall to be used. Prior to homestead, only a limited variant called `callcode` was available that did not provide access to the original `msg.sender` and `msg.value` values.

Both `call` and `callcode` are very low-level functions and should only be used as a *last resort* as they break the type-safety of Solidity.
All three functions `call`, `delegatecall` and `callcode` are very low-level functions and should only be used as a *last resort* as they break the type-safety of Solidity.

.. note::
All contracts inherit the members of address, so it is possible to query the balance of the
Expand Down
47 changes: 24 additions & 23 deletions libsolidity/ast/Types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 && m_location != Location::DelegateCall)
members.push_back(MemberList::Member(
"value",
make_shared<FunctionType>(
parseElementaryTypeVector({"uint"}),
Expand All @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand Down
4 changes: 3 additions & 1 deletion libsolidity/ast/Types.h
Original file line number Diff line number Diff line change
Expand Up @@ -732,8 +732,10 @@ class FunctionType: public Type
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
Expand Down Expand Up @@ -869,7 +871,7 @@ class FunctionType: public Type
/// 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;

Expand Down
10 changes: 4 additions & 6 deletions libsolidity/codegen/Compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion libsolidity/codegen/Compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class Compiler: private ASTConstVisitor
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,
Expand Down
29 changes: 18 additions & 11 deletions libsolidity/codegen/ExpressionCompiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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;
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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)
Expand All @@ -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(
Expand Down Expand Up @@ -1416,7 +1419,7 @@ void ExpressionCompiler::appendExternalFunctionCall(
parameterTypes,
_functionType.padArguments(),
_functionType.takesArbitraryParameters(),
isCallCode
isCallCode || isDelegateCall
);

// Stack now:
Expand All @@ -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);
Expand All @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion libsolidity/interface/CompilerStack.h
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ class CompilerStack: boost::noncopyable
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.
Expand Down
Loading