mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
88c99a7538
And also making the type of address(literal) as non-payable address.
370 lines
11 KiB
ReStructuredText
370 lines
11 KiB
ReStructuredText
###############
|
|
Common Patterns
|
|
###############
|
|
|
|
.. index:: withdrawal
|
|
|
|
.. _withdrawal_pattern:
|
|
|
|
*************************
|
|
Withdrawal from Contracts
|
|
*************************
|
|
|
|
The recommended method of sending funds after an effect
|
|
is using the withdrawal pattern. Although the most intuitive
|
|
method of sending Ether, as a result of an effect, is a
|
|
direct ``transfer`` call, this is not recommended as it
|
|
introduces a potential security risk. You may read
|
|
more about this on the :ref:`security_considerations` page.
|
|
|
|
The following is an example of the withdrawal pattern in practice in
|
|
a contract where the goal is to send the most money to the
|
|
contract in order to become the "richest", inspired by
|
|
`King of the Ether <https://www.kingoftheether.com/>`_.
|
|
|
|
In the following contract, if you are no longer the richest,
|
|
you receive the funds of the person who is now the richest.
|
|
|
|
::
|
|
|
|
// SPDX-License-Identifier: GPL-3.0
|
|
pragma solidity >=0.7.0 <0.9.0;
|
|
|
|
contract WithdrawalContract {
|
|
address public richest;
|
|
uint public mostSent;
|
|
|
|
mapping (address => uint) pendingWithdrawals;
|
|
|
|
constructor() payable {
|
|
richest = msg.sender;
|
|
mostSent = msg.value;
|
|
}
|
|
|
|
function becomeRichest() public payable {
|
|
require(msg.value > mostSent, "Not enough money sent.");
|
|
pendingWithdrawals[richest] += msg.value;
|
|
richest = msg.sender;
|
|
mostSent = msg.value;
|
|
}
|
|
|
|
function withdraw() public {
|
|
uint amount = pendingWithdrawals[msg.sender];
|
|
// Remember to zero the pending refund before
|
|
// sending to prevent re-entrancy attacks
|
|
pendingWithdrawals[msg.sender] = 0;
|
|
payable(msg.sender).transfer(amount);
|
|
}
|
|
}
|
|
|
|
This is as opposed to the more intuitive sending pattern:
|
|
|
|
::
|
|
|
|
// SPDX-License-Identifier: GPL-3.0
|
|
pragma solidity >=0.7.0 <0.9.0;
|
|
|
|
contract SendContract {
|
|
address payable public richest;
|
|
uint public mostSent;
|
|
|
|
constructor() payable {
|
|
richest = payable(msg.sender);
|
|
mostSent = msg.value;
|
|
}
|
|
|
|
function becomeRichest() public payable {
|
|
require(msg.value > mostSent, "Not enough money sent.");
|
|
// This line can cause problems (explained below).
|
|
richest.transfer(msg.value);
|
|
richest = payable(msg.sender);
|
|
mostSent = msg.value;
|
|
}
|
|
}
|
|
|
|
Notice that, in this example, an attacker could trap the
|
|
contract into an unusable state by causing ``richest`` to be
|
|
the address of a contract that has a receive or fallback function
|
|
which fails (e.g. by using ``revert()`` or by just
|
|
consuming more than the 2300 gas stipend transferred to them). That way,
|
|
whenever ``transfer`` is called to deliver funds to the
|
|
"poisoned" contract, it will fail and thus also ``becomeRichest``
|
|
will fail, with the contract being stuck forever.
|
|
|
|
In contrast, if you use the "withdraw" pattern from the first example,
|
|
the attacker can only cause his or her own withdraw to fail and not the
|
|
rest of the contract's workings.
|
|
|
|
.. index:: access;restricting
|
|
|
|
******************
|
|
Restricting Access
|
|
******************
|
|
|
|
Restricting access is a common pattern for contracts.
|
|
Note that you can never restrict any human or computer
|
|
from reading the content of your transactions or
|
|
your contract's state. You can make it a bit harder
|
|
by using encryption, but if your contract is supposed
|
|
to read the data, so will everyone else.
|
|
|
|
You can restrict read access to your contract's state
|
|
by **other contracts**. That is actually the default
|
|
unless you declare your state variables ``public``.
|
|
|
|
Furthermore, you can restrict who can make modifications
|
|
to your contract's state or call your contract's
|
|
functions and this is what this section is about.
|
|
|
|
.. index:: function;modifier
|
|
|
|
The use of **function modifiers** makes these
|
|
restrictions highly readable.
|
|
|
|
::
|
|
|
|
// SPDX-License-Identifier: GPL-3.0
|
|
pragma solidity >=0.6.0 <0.9.0;
|
|
|
|
contract AccessRestriction {
|
|
// These will be assigned at the construction
|
|
// phase, where `msg.sender` is the account
|
|
// creating this contract.
|
|
address public owner = msg.sender;
|
|
uint public creationTime = block.timestamp;
|
|
|
|
// Modifiers can be used to change
|
|
// the body of a function.
|
|
// If this modifier is used, it will
|
|
// prepend a check that only passes
|
|
// if the function is called from
|
|
// a certain address.
|
|
modifier onlyBy(address _account)
|
|
{
|
|
require(
|
|
msg.sender == _account,
|
|
"Sender not authorized."
|
|
);
|
|
// Do not forget the "_;"! It will
|
|
// be replaced by the actual function
|
|
// body when the modifier is used.
|
|
_;
|
|
}
|
|
|
|
/// Make `_newOwner` the new owner of this
|
|
/// contract.
|
|
function changeOwner(address _newOwner)
|
|
public
|
|
onlyBy(owner)
|
|
{
|
|
owner = _newOwner;
|
|
}
|
|
|
|
modifier onlyAfter(uint _time) {
|
|
require(
|
|
block.timestamp >= _time,
|
|
"Function called too early."
|
|
);
|
|
_;
|
|
}
|
|
|
|
/// Erase ownership information.
|
|
/// May only be called 6 weeks after
|
|
/// the contract has been created.
|
|
function disown()
|
|
public
|
|
onlyBy(owner)
|
|
onlyAfter(creationTime + 6 weeks)
|
|
{
|
|
delete owner;
|
|
}
|
|
|
|
// This modifier requires a certain
|
|
// fee being associated with a function call.
|
|
// If the caller sent too much, he or she is
|
|
// refunded, but only after the function body.
|
|
// This was dangerous before Solidity version 0.4.0,
|
|
// where it was possible to skip the part after `_;`.
|
|
modifier costs(uint _amount) {
|
|
require(
|
|
msg.value >= _amount,
|
|
"Not enough Ether provided."
|
|
);
|
|
_;
|
|
if (msg.value > _amount)
|
|
payable(msg.sender).transfer(msg.value - _amount);
|
|
}
|
|
|
|
function forceOwnerChange(address _newOwner)
|
|
public
|
|
payable
|
|
costs(200 ether)
|
|
{
|
|
owner = _newOwner;
|
|
// just some example condition
|
|
if (uint160(owner) & 0 == 1)
|
|
// This did not refund for Solidity
|
|
// before version 0.4.0.
|
|
return;
|
|
// refund overpaid fees
|
|
}
|
|
}
|
|
|
|
A more specialised way in which access to function
|
|
calls can be restricted will be discussed
|
|
in the next example.
|
|
|
|
.. index:: state machine
|
|
|
|
*************
|
|
State Machine
|
|
*************
|
|
|
|
Contracts often act as a state machine, which means
|
|
that they have certain **stages** in which they behave
|
|
differently or in which different functions can
|
|
be called. A function call often ends a stage
|
|
and transitions the contract into the next stage
|
|
(especially if the contract models **interaction**).
|
|
It is also common that some stages are automatically
|
|
reached at a certain point in **time**.
|
|
|
|
An example for this is a blind auction contract which
|
|
starts in the stage "accepting blinded bids", then
|
|
transitions to "revealing bids" which is ended by
|
|
"determine auction outcome".
|
|
|
|
.. index:: function;modifier
|
|
|
|
Function modifiers can be used in this situation
|
|
to model the states and guard against
|
|
incorrect usage of the contract.
|
|
|
|
Example
|
|
=======
|
|
|
|
In the following example,
|
|
the modifier ``atStage`` ensures that the function can
|
|
only be called at a certain stage.
|
|
|
|
Automatic timed transitions
|
|
are handled by the modifier ``timeTransitions``, which
|
|
should be used for all functions.
|
|
|
|
.. note::
|
|
**Modifier Order Matters**.
|
|
If atStage is combined
|
|
with timedTransitions, make sure that you mention
|
|
it after the latter, so that the new stage is
|
|
taken into account.
|
|
|
|
Finally, the modifier ``transitionNext`` can be used
|
|
to automatically go to the next stage when the
|
|
function finishes.
|
|
|
|
.. note::
|
|
**Modifier May be Skipped**.
|
|
This only applies to Solidity before version 0.4.0:
|
|
Since modifiers are applied by simply replacing
|
|
code and not by using a function call,
|
|
the code in the transitionNext modifier
|
|
can be skipped if the function itself uses
|
|
return. If you want to do that, make sure
|
|
to call nextStage manually from those functions.
|
|
Starting with version 0.4.0, modifier code
|
|
will run even if the function explicitly returns.
|
|
|
|
::
|
|
|
|
// SPDX-License-Identifier: GPL-3.0
|
|
pragma solidity >=0.4.22 <0.9.0;
|
|
|
|
contract StateMachine {
|
|
enum Stages {
|
|
AcceptingBlindedBids,
|
|
RevealBids,
|
|
AnotherStage,
|
|
AreWeDoneYet,
|
|
Finished
|
|
}
|
|
|
|
// This is the current stage.
|
|
Stages public stage = Stages.AcceptingBlindedBids;
|
|
|
|
uint public creationTime = block.timestamp;
|
|
|
|
modifier atStage(Stages _stage) {
|
|
require(
|
|
stage == _stage,
|
|
"Function cannot be called at this time."
|
|
);
|
|
_;
|
|
}
|
|
|
|
function nextStage() internal {
|
|
stage = Stages(uint(stage) + 1);
|
|
}
|
|
|
|
// Perform timed transitions. Be sure to mention
|
|
// this modifier first, otherwise the guards
|
|
// will not take the new stage into account.
|
|
modifier timedTransitions() {
|
|
if (stage == Stages.AcceptingBlindedBids &&
|
|
block.timestamp >= creationTime + 10 days)
|
|
nextStage();
|
|
if (stage == Stages.RevealBids &&
|
|
block.timestamp >= creationTime + 12 days)
|
|
nextStage();
|
|
// The other stages transition by transaction
|
|
_;
|
|
}
|
|
|
|
// Order of the modifiers matters here!
|
|
function bid()
|
|
public
|
|
payable
|
|
timedTransitions
|
|
atStage(Stages.AcceptingBlindedBids)
|
|
{
|
|
// We will not implement that here
|
|
}
|
|
|
|
function reveal()
|
|
public
|
|
timedTransitions
|
|
atStage(Stages.RevealBids)
|
|
{
|
|
}
|
|
|
|
// This modifier goes to the next stage
|
|
// after the function is done.
|
|
modifier transitionNext()
|
|
{
|
|
_;
|
|
nextStage();
|
|
}
|
|
|
|
function g()
|
|
public
|
|
timedTransitions
|
|
atStage(Stages.AnotherStage)
|
|
transitionNext
|
|
{
|
|
}
|
|
|
|
function h()
|
|
public
|
|
timedTransitions
|
|
atStage(Stages.AreWeDoneYet)
|
|
transitionNext
|
|
{
|
|
}
|
|
|
|
function i()
|
|
public
|
|
timedTransitions
|
|
atStage(Stages.Finished)
|
|
{
|
|
}
|
|
}
|