mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Add payable and non-payable state mutability to AddressType.
This commit is contained in:
parent
9214c7c34f
commit
12aaca1645
@ -7,6 +7,7 @@ How to update your code:
|
||||
* Make your fallback functions ``external``.
|
||||
* Explicitly state the data location for all variables of struct, array or mapping types (including function parameters), e.g. change ``uint[] x = m_x`` to ``uint[] storage x = m_x``. Note that ``external`` functions require parameters with a data location of ``calldata``.
|
||||
* Explicitly convert values of contract type to addresses before using an ``address`` member. Example: if ``c`` is a contract, change ``c.transfer(...)`` to ``address(c).transfer(...)``.
|
||||
* Declare variables and especially function arguments as ``address payable``, if you want to call ``transfer`` on them.
|
||||
|
||||
Breaking Changes:
|
||||
* ABI Encoder: Properly pad data from calldata (``msg.data`` and external function parameters). Use ``abi.encodePacked`` for unpadded encoding.
|
||||
@ -62,6 +63,7 @@ Breaking Changes:
|
||||
* Type Checker: Disallow "loose assembly" syntax entirely. This means that jump labels, jumps and non-functional instructions cannot be used anymore.
|
||||
* Type System: Disallow explicit and implicit conversions from decimal literals to ``bytesXX`` types.
|
||||
* Type System: Disallow explicit and implicit conversions from hex literals to ``bytesXX`` types of different size.
|
||||
* Type System: Distinguish between payable and non-payable address types.
|
||||
* View Pure Checker: Disallow ``msg.value`` in (or introducing it via a modifier to) a non-payable function.
|
||||
* Remove obsolete ``std`` directory from the Solidity repository. This means accessing ``https://github.com/ethereum/solidity/blob/develop/std/*.sol`` (or ``https://github.com/ethereum/solidity/std/*.sol`` in Remix) will not be possible.
|
||||
* References Resolver: Turn missing storage locations into an error. This was already the case in the experimental 0.5.0 mode.
|
||||
|
@ -68,7 +68,7 @@ This is as opposed to the more intuitive sending pattern:
|
||||
pragma solidity >0.4.24;
|
||||
|
||||
contract SendContract {
|
||||
address public richest;
|
||||
address payable public richest;
|
||||
uint public mostSent;
|
||||
|
||||
constructor() public payable {
|
||||
|
@ -334,7 +334,7 @@ inheritable properties of contracts and may be overridden by derived contracts.
|
||||
|
||||
contract owned {
|
||||
constructor() public { owner = msg.sender; }
|
||||
address owner;
|
||||
address payable owner;
|
||||
|
||||
// This contract only defines a modifier but does not use
|
||||
// it: it will be used in derived contracts.
|
||||
@ -650,9 +650,14 @@ Like any function, the fallback function can execute complex operations as long
|
||||
require(success);
|
||||
// results in test.x becoming == 1.
|
||||
|
||||
// address(test) will not allow to call ``send`` directly, since ``test`` has no payable
|
||||
// fallback function. It has to be converted to the ``address payable`` type via an
|
||||
// intermediate conversion to ``uint160`` to even allow calling ``send`` on it.
|
||||
address payable testPayable = address(uint160(address(test)));
|
||||
|
||||
// If someone sends ether to that contract,
|
||||
// the transfer will fail, i.e. this returns false here.
|
||||
return address(test).send(2 ether);
|
||||
return testPayable.send(2 ether);
|
||||
}
|
||||
}
|
||||
|
||||
@ -891,7 +896,7 @@ Details are given in the following example.
|
||||
|
||||
contract owned {
|
||||
constructor() public { owner = msg.sender; }
|
||||
address owner;
|
||||
address payable owner;
|
||||
}
|
||||
|
||||
// Use `is` to derive from another contract. Derived
|
||||
@ -963,7 +968,7 @@ seen in the following example::
|
||||
|
||||
contract owned {
|
||||
constructor() public { owner = msg.sender; }
|
||||
address owner;
|
||||
address payable owner;
|
||||
}
|
||||
|
||||
contract mortal is owned {
|
||||
@ -992,7 +997,7 @@ derived override, but this function will bypass
|
||||
|
||||
contract owned {
|
||||
constructor() public { owner = msg.sender; }
|
||||
address owner;
|
||||
address payable owner;
|
||||
}
|
||||
|
||||
contract mortal is owned {
|
||||
|
@ -421,7 +421,7 @@ a message string for ``require``, but not for ``assert``.
|
||||
pragma solidity >0.4.24;
|
||||
|
||||
contract Sharer {
|
||||
function sendHalf(address addr) public payable returns (uint balance) {
|
||||
function sendHalf(address payable addr) public payable returns (uint balance) {
|
||||
require(msg.value % 2 == 0, "Even value required.");
|
||||
uint balanceBeforeTransfer = address(this).balance;
|
||||
addr.transfer(msg.value / 2);
|
||||
|
@ -329,7 +329,7 @@ Global Variables
|
||||
starting from the second and prepends the given four-byte selector
|
||||
- ``abi.encodeWithSignature(string signature, ...) returns (bytes)``: Equivalent to ``abi.encodeWithSelector(bytes4(keccak256(bytes(signature)), ...)```
|
||||
- ``block.blockhash(uint blockNumber) returns (bytes32)``: hash of the given block - only works for 256 most recent, excluding current, blocks - deprecated in version 0.4.22 and replaced by ``blockhash(uint blockNumber)``.
|
||||
- ``block.coinbase`` (``address``): current block miner's address
|
||||
- ``block.coinbase`` (``address payable``): current block miner's address
|
||||
- ``block.difficulty`` (``uint``): current block difficulty
|
||||
- ``block.gaslimit`` (``uint``): current block gaslimit
|
||||
- ``block.number`` (``uint``): current block number
|
||||
@ -337,11 +337,11 @@ Global Variables
|
||||
- ``gasleft() returns (uint256)``: remaining gas
|
||||
- ``msg.data`` (``bytes``): complete calldata
|
||||
- ``msg.gas`` (``uint``): remaining gas - deprecated in version 0.4.21 and to be replaced by ``gasleft()``
|
||||
- ``msg.sender`` (``address``): sender of the message (current call)
|
||||
- ``msg.sender`` (``address payable``): sender of the message (current call)
|
||||
- ``msg.value`` (``uint``): number of wei sent with the message
|
||||
- ``now`` (``uint``): current block timestamp (alias for ``block.timestamp``)
|
||||
- ``tx.gasprice`` (``uint``): gas price of the transaction
|
||||
- ``tx.origin`` (``address``): sender of the transaction (full call chain)
|
||||
- ``tx.origin`` (``address payable``): sender of the transaction (full call chain)
|
||||
- ``assert(bool condition)``: abort execution and revert state changes if condition is ``false`` (use for internal error)
|
||||
- ``require(bool condition)``: abort execution and revert state changes if condition is ``false`` (use for malformed input or error in external component)
|
||||
- ``require(bool condition, string message)``: abort execution and revert state changes if condition is ``false`` (use for malformed input or error in external component). Also provide error message.
|
||||
@ -360,8 +360,8 @@ Global Variables
|
||||
- ``selfdestruct(address recipient)``: destroy the current contract, sending its funds to the given address
|
||||
- ``suicide(address recipient)``: a deprecated alias to ``selfdestruct``
|
||||
- ``<address>.balance`` (``uint256``): balance of the :ref:`address` in Wei
|
||||
- ``<address>.send(uint256 amount) returns (bool)``: send given amount of Wei to :ref:`address`, returns ``false`` on failure
|
||||
- ``<address>.transfer(uint256 amount)``: send given amount of Wei to :ref:`address`, throws on failure
|
||||
- ``<address payable>.send(uint256 amount) returns (bool)``: send given amount of Wei to :ref:`address`, returns ``false`` on failure
|
||||
- ``<address payable>.transfer(uint256 amount)``: send given amount of Wei to :ref:`address`, throws on failure
|
||||
|
||||
.. note::
|
||||
Do not rely on ``block.timestamp``, ``now`` and ``blockhash`` as a source of randomness,
|
||||
|
@ -192,7 +192,7 @@ Never use tx.origin for authorization. Let's say you have a wallet contract like
|
||||
owner = msg.sender;
|
||||
}
|
||||
|
||||
function transferTo(address dest, uint amount) public {
|
||||
function transferTo(address payable dest, uint amount) public {
|
||||
require(tx.origin == owner);
|
||||
dest.transfer(amount);
|
||||
}
|
||||
@ -205,11 +205,11 @@ Now someone tricks you into sending ether to the address of this attack wallet:
|
||||
pragma solidity >0.4.24;
|
||||
|
||||
interface TxUserWallet {
|
||||
function transferTo(address dest, uint amount) external;
|
||||
function transferTo(address payable dest, uint amount) external;
|
||||
}
|
||||
|
||||
contract TxAttackWallet {
|
||||
address owner;
|
||||
address payable owner;
|
||||
|
||||
constructor() public {
|
||||
owner = msg.sender;
|
||||
|
@ -231,7 +231,7 @@ activate themselves.
|
||||
// Parameters of the auction. Times are either
|
||||
// absolute unix timestamps (seconds since 1970-01-01)
|
||||
// or time periods in seconds.
|
||||
address public beneficiary;
|
||||
address payable public beneficiary;
|
||||
uint public auctionEndTime;
|
||||
|
||||
// Current state of the auction.
|
||||
@ -258,7 +258,7 @@ activate themselves.
|
||||
/// beneficiary address `_beneficiary`.
|
||||
constructor(
|
||||
uint _biddingTime,
|
||||
address _beneficiary
|
||||
address payable _beneficiary
|
||||
) public {
|
||||
beneficiary = _beneficiary;
|
||||
auctionEndTime = now + _biddingTime;
|
||||
@ -396,7 +396,7 @@ high or low invalid bids.
|
||||
uint deposit;
|
||||
}
|
||||
|
||||
address public beneficiary;
|
||||
address payable public beneficiary;
|
||||
uint public biddingEnd;
|
||||
uint public revealEnd;
|
||||
bool public ended;
|
||||
@ -421,7 +421,7 @@ high or low invalid bids.
|
||||
constructor(
|
||||
uint _biddingTime,
|
||||
uint _revealTime,
|
||||
address _beneficiary
|
||||
address payable _beneficiary
|
||||
) public {
|
||||
beneficiary = _beneficiary;
|
||||
biddingEnd = now + _biddingTime;
|
||||
@ -545,8 +545,8 @@ Safe Remote Purchase
|
||||
|
||||
contract Purchase {
|
||||
uint public value;
|
||||
address public seller;
|
||||
address public buyer;
|
||||
address payable public seller;
|
||||
address payable public buyer;
|
||||
enum State { Created, Locked, Inactive }
|
||||
State public state;
|
||||
|
||||
@ -990,11 +990,11 @@ The full contract
|
||||
pragma solidity ^0.4.24;
|
||||
|
||||
contract SimplePaymentChannel {
|
||||
address public sender; // The account sending payments.
|
||||
address public recipient; // The account receiving the payments.
|
||||
address payable public sender; // The account sending payments.
|
||||
address payable public recipient; // The account receiving the payments.
|
||||
uint256 public expiration; // Timeout in case the recipient never closes.
|
||||
|
||||
constructor (address _recipient, uint256 duration)
|
||||
constructor (address payable _recipient, uint256 duration)
|
||||
public
|
||||
payable
|
||||
{
|
||||
|
@ -99,6 +99,15 @@ Address
|
||||
-------
|
||||
|
||||
``address``: Holds a 20 byte value (size of an Ethereum address). Address types also have members and serve as a base for all contracts.
|
||||
``address payable``: Same as ``address``, but with the additional members ``transfer`` and ``send``.
|
||||
|
||||
Implicit conversions from ``address payable`` to ``address`` are allowed, whereas conversions from ``address`` to ``address payable`` are
|
||||
not possible (the only way to perform such a conversion is by using an intermediate conversion to ``uint160``).
|
||||
Conversions of the form ``address payable(x)`` are not allowed. Instead the result of a conversion of the form ``address(x)``
|
||||
has the type ``address payable``, if ``x`` is of integer or fixed bytes type, a literal or a contract with a payable fallback function.
|
||||
If ``x`` is a contract without payable fallback function ``address(x)`` will be of type ``address``. The type of address literals
|
||||
is ``address payable``.
|
||||
In external function signatures ``address`` is used for both the ``address`` and the ``address payable`` type.
|
||||
|
||||
Operators:
|
||||
|
||||
@ -113,7 +122,8 @@ Operators:
|
||||
or you can use ``address(uint160(uint256(b)))``, which results in ``0x777788889999AaAAbBbbCcccddDdeeeEfFFfCcCc``.
|
||||
|
||||
.. note::
|
||||
Starting with version 0.5.0 contracts do not derive from the address type, but can still be explicitly converted to address.
|
||||
Starting with version 0.5.0 contracts do not derive from the address type, but can still be explicitly converted to
|
||||
``address`` or to ``address payable``, if they have a payable fallback function.
|
||||
|
||||
.. _members-of-addresses:
|
||||
|
||||
@ -125,11 +135,11 @@ Members of Addresses
|
||||
For a quick reference, see :ref:`address_related`.
|
||||
|
||||
It is possible to query the balance of an address using the property ``balance``
|
||||
and to send Ether (in units of wei) to an address using the ``transfer`` function:
|
||||
and to send Ether (in units of wei) to a payable address using the ``transfer`` function:
|
||||
|
||||
::
|
||||
|
||||
address x = 0x123;
|
||||
address payable x = address(0x123);
|
||||
address myAddress = this;
|
||||
if (x.balance < 10 && myAddress.balance >= 10) x.transfer(10);
|
||||
|
||||
@ -211,11 +221,14 @@ Contract Types
|
||||
|
||||
Every :ref:`contract<contracts>` defines its own type.
|
||||
You can implicitly convert contracts to contracts they inherit from,
|
||||
and explicitly convert them to and from the ``address`` type.
|
||||
and explicitly convert them to and from the ``address`` type, if they have no
|
||||
payable fallback functions, or to and from the ``address payable`` type, if they do
|
||||
have payable fallback functions.
|
||||
|
||||
.. note::
|
||||
Starting with version 0.5.0 contracts do not derive from the address type,
|
||||
but can still be explicitly converted to address.
|
||||
but can still be explicitly converted to ``address``, resp. to ``address payable``,
|
||||
if they have a payable fallback function.
|
||||
|
||||
If you declare a local variable of contract type (`MyContract c`), you can call
|
||||
functions on that contract. Take care to assign it from somewhere that is the
|
||||
@ -860,7 +873,7 @@ shown in the following example:
|
||||
}
|
||||
|
||||
struct Campaign {
|
||||
address beneficiary;
|
||||
address payable beneficiary;
|
||||
uint fundingGoal;
|
||||
uint numFunders;
|
||||
uint amount;
|
||||
@ -870,7 +883,7 @@ shown in the following example:
|
||||
uint numCampaigns;
|
||||
mapping (uint => Campaign) campaigns;
|
||||
|
||||
function newCampaign(address beneficiary, uint goal) public returns (uint campaignID) {
|
||||
function newCampaign(address payable beneficiary, uint goal) public returns (uint campaignID) {
|
||||
campaignID = numCampaigns++; // campaignID is return variable
|
||||
// Creates new struct and saves in storage. We leave out the mapping type.
|
||||
campaigns[campaignID] = Campaign(beneficiary, goal, 0, 0);
|
||||
|
@ -57,7 +57,7 @@ Block and Transaction Properties
|
||||
--------------------------------
|
||||
|
||||
- ``block.blockhash(uint blockNumber) returns (bytes32)``: hash of the given block - only works for 256 most recent, excluding current, blocks - deprecated in version 0.4.22 and replaced by ``blockhash(uint blockNumber)``.
|
||||
- ``block.coinbase`` (``address``): current block miner's address
|
||||
- ``block.coinbase`` (``address payable``): current block miner's address
|
||||
- ``block.difficulty`` (``uint``): current block difficulty
|
||||
- ``block.gaslimit`` (``uint``): current block gaslimit
|
||||
- ``block.number`` (``uint``): current block number
|
||||
@ -65,12 +65,12 @@ Block and Transaction Properties
|
||||
- ``gasleft() returns (uint256)``: remaining gas
|
||||
- ``msg.data`` (``bytes``): complete calldata
|
||||
- ``msg.gas`` (``uint``): remaining gas - deprecated in version 0.4.21 and to be replaced by ``gasleft()``
|
||||
- ``msg.sender`` (``address``): sender of the message (current call)
|
||||
- ``msg.sender`` (``address payable``): sender of the message (current call)
|
||||
- ``msg.sig`` (``bytes4``): first four bytes of the calldata (i.e. function identifier)
|
||||
- ``msg.value`` (``uint``): number of wei sent with the message
|
||||
- ``now`` (``uint``): current block timestamp (alias for ``block.timestamp``)
|
||||
- ``tx.gasprice`` (``uint``): gas price of the transaction
|
||||
- ``tx.origin`` (``address``): sender of the transaction (full call chain)
|
||||
- ``tx.origin`` (``address payable``): sender of the transaction (full call chain)
|
||||
|
||||
.. note::
|
||||
The values of all members of ``msg``, including ``msg.sender`` and
|
||||
@ -161,9 +161,9 @@ Address Related
|
||||
|
||||
``<address>.balance`` (``uint256``):
|
||||
balance of the :ref:`address` in Wei
|
||||
``<address>.transfer(uint256 amount)``:
|
||||
``<address payable>.transfer(uint256 amount)``:
|
||||
send given amount of Wei to :ref:`address`, throws on failure, forwards 2300 gas stipend, not adjustable
|
||||
``<address>.send(uint256 amount) returns (bool)``:
|
||||
``<address payable>.send(uint256 amount) returns (bool)``:
|
||||
send given amount of Wei to :ref:`address`, returns ``false`` on failure, forwards 2300 gas stipend, not adjustable
|
||||
``<address>.call(bytes memory) returns (bool)``:
|
||||
issue low-level ``CALL`` with the given payload, returns ``false`` on failure, forwards all available gas, adjustable
|
||||
@ -204,10 +204,10 @@ Contract Related
|
||||
``this`` (current contract's type):
|
||||
the current contract, explicitly convertible to :ref:`address`
|
||||
|
||||
``selfdestruct(address recipient)``:
|
||||
``selfdestruct(address payable recipient)``:
|
||||
destroy the current contract, sending its funds to the given :ref:`address`
|
||||
|
||||
``suicide(address recipient)``:
|
||||
``suicide(address payable recipient)``:
|
||||
deprecated alias to ``selfdestruct``
|
||||
|
||||
Furthermore, all functions of the current contract are callable directly including the current function.
|
||||
|
@ -56,10 +56,10 @@ m_magicVariables(vector<shared_ptr<MagicVariableDeclaration const>>{
|
||||
make_shared<MagicVariableDeclaration>("revert", make_shared<FunctionType>(strings(), strings(), FunctionType::Kind::Revert, false, StateMutability::Pure)),
|
||||
make_shared<MagicVariableDeclaration>("revert", make_shared<FunctionType>(strings{"string memory"}, strings(), FunctionType::Kind::Revert, false, StateMutability::Pure)),
|
||||
make_shared<MagicVariableDeclaration>("ripemd160", make_shared<FunctionType>(strings{"bytes memory"}, strings{"bytes20"}, FunctionType::Kind::RIPEMD160, false, StateMutability::Pure)),
|
||||
make_shared<MagicVariableDeclaration>("selfdestruct", make_shared<FunctionType>(strings{"address"}, strings{}, FunctionType::Kind::Selfdestruct)),
|
||||
make_shared<MagicVariableDeclaration>("selfdestruct", make_shared<FunctionType>(strings{"address payable"}, strings{}, FunctionType::Kind::Selfdestruct)),
|
||||
make_shared<MagicVariableDeclaration>("sha256", make_shared<FunctionType>(strings{"bytes memory"}, strings{"bytes32"}, FunctionType::Kind::SHA256, false, StateMutability::Pure)),
|
||||
make_shared<MagicVariableDeclaration>("sha3", make_shared<FunctionType>(strings{"bytes memory"}, strings{"bytes32"}, FunctionType::Kind::KECCAK256, false, StateMutability::Pure)),
|
||||
make_shared<MagicVariableDeclaration>("suicide", make_shared<FunctionType>(strings{"address"}, strings{}, FunctionType::Kind::Selfdestruct)),
|
||||
make_shared<MagicVariableDeclaration>("suicide", make_shared<FunctionType>(strings{"address payable"}, strings{}, FunctionType::Kind::Selfdestruct)),
|
||||
make_shared<MagicVariableDeclaration>("tx", make_shared<MagicType>(MagicType::Kind::Transaction))
|
||||
})
|
||||
{
|
||||
|
@ -119,11 +119,19 @@ bool ReferencesResolver::visit(ElementaryTypeName const& _typeName)
|
||||
{
|
||||
// for non-address types this was already caught by the parser
|
||||
solAssert(_typeName.annotation().type->category() == Type::Category::Address, "");
|
||||
if (!(
|
||||
*_typeName.stateMutability() == StateMutability::Payable ||
|
||||
*_typeName.stateMutability() == StateMutability::NonPayable
|
||||
))
|
||||
m_errorReporter.typeError(_typeName.location(), "Address types can only be payable or non-payable.");
|
||||
switch(*_typeName.stateMutability())
|
||||
{
|
||||
case StateMutability::Payable:
|
||||
case StateMutability::NonPayable:
|
||||
_typeName.annotation().type = make_shared<AddressType>(*_typeName.stateMutability());
|
||||
break;
|
||||
default:
|
||||
m_errorReporter.typeError(
|
||||
_typeName.location(),
|
||||
"Address types can only be payable or non-payable."
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
@ -571,6 +571,9 @@ TypePointers TypeChecker::typeCheckABIDecodeAndRetrieveReturnType(FunctionCall c
|
||||
// data locations. Furthermore, storage can be a little dangerous and
|
||||
// calldata is not really implemented anyway.
|
||||
actualType = ReferenceType::copyForLocationIfReference(DataLocation::Memory, actualType);
|
||||
// We force address payable for address types.
|
||||
if (actualType->category() == Type::Category::Address)
|
||||
actualType = make_shared<AddressType>(StateMutability::Payable);
|
||||
solAssert(
|
||||
!actualType->dataStoredIn(DataLocation::CallData) &&
|
||||
!actualType->dataStoredIn(DataLocation::Storage),
|
||||
@ -1732,14 +1735,39 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
|
||||
dataLoc = argRefType->location();
|
||||
resultType = ReferenceType::copyForLocationIfReference(dataLoc, resultType);
|
||||
if (!argType->isExplicitlyConvertibleTo(*resultType))
|
||||
m_errorReporter.typeError(
|
||||
_functionCall.location(),
|
||||
"Explicit type conversion not allowed from \"" +
|
||||
argType->toString() +
|
||||
"\" to \"" +
|
||||
resultType->toString() +
|
||||
"\"."
|
||||
);
|
||||
{
|
||||
if (resultType->category() == Type::Category::Contract && argType->category() == Type::Category::Address)
|
||||
{
|
||||
solAssert(dynamic_cast<ContractType const*>(resultType.get())->isPayable(), "");
|
||||
solAssert(dynamic_cast<AddressType const*>(argType.get())->stateMutability() < StateMutability::Payable, "");
|
||||
SecondarySourceLocation ssl;
|
||||
if (auto const* identifier = dynamic_cast<Identifier const*>(arguments.front().get()))
|
||||
if (auto const* variableDeclaration = dynamic_cast<VariableDeclaration const*>(identifier->annotation().referencedDeclaration))
|
||||
ssl.append("Did you mean to declare this variable as \"address payable\"?", variableDeclaration->location());
|
||||
m_errorReporter.typeError(
|
||||
_functionCall.location(), ssl,
|
||||
"Explicit type conversion not allowed from non-payable \"address\" to \"" +
|
||||
resultType->toString() +
|
||||
"\", which has a payable fallback function."
|
||||
);
|
||||
}
|
||||
else
|
||||
m_errorReporter.typeError(
|
||||
_functionCall.location(),
|
||||
"Explicit type conversion not allowed from \"" +
|
||||
argType->toString() +
|
||||
"\" to \"" +
|
||||
resultType->toString() +
|
||||
"\"."
|
||||
);
|
||||
}
|
||||
if (resultType->category() == Type::Category::Address)
|
||||
{
|
||||
bool payable = true;
|
||||
if (auto const* contractType = dynamic_cast<ContractType const*>(argType.get()))
|
||||
payable = contractType->isPayable();
|
||||
resultType = make_shared<AddressType>(payable ? StateMutability::Payable : StateMutability::NonPayable);
|
||||
}
|
||||
}
|
||||
_functionCall.annotation().type = resultType;
|
||||
_functionCall.annotation().isPure = isPure;
|
||||
@ -2103,7 +2131,7 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
|
||||
"after argument-dependent lookup in " + exprType->toString() +
|
||||
(memberName == "value" ? " - did you forget the \"payable\" modifier?" : ".");
|
||||
if (exprType->category() == Type::Category::Contract)
|
||||
for (auto const& addressMember: AddressType().nativeMembers(nullptr))
|
||||
for (auto const& addressMember: AddressType(StateMutability::Payable).nativeMembers(nullptr))
|
||||
if (addressMember.name == memberName)
|
||||
{
|
||||
Identifier const* var = dynamic_cast<Identifier const*>(&_memberAccess.expression());
|
||||
@ -2354,7 +2382,7 @@ void TypeChecker::endVisit(Literal const& _literal)
|
||||
if (_literal.looksLikeAddress())
|
||||
{
|
||||
// Assign type here if it even looks like an address. This prevents double errors for invalid addresses
|
||||
_literal.annotation().type = make_shared<AddressType>();
|
||||
_literal.annotation().type = make_shared<AddressType>(StateMutability::Payable);
|
||||
|
||||
string msg;
|
||||
if (_literal.value().length() != 42) // "0x" + 40 hex digits
|
||||
|
@ -299,7 +299,7 @@ TypePointer Type::fromElementaryTypeName(ElementaryTypeNameToken const& _type)
|
||||
case Token::Byte:
|
||||
return make_shared<FixedBytesType>(1);
|
||||
case Token::Address:
|
||||
return make_shared<AddressType>();
|
||||
return make_shared<AddressType>(StateMutability::NonPayable);
|
||||
case Token::Bool:
|
||||
return make_shared<BoolType>();
|
||||
case Token::Bytes:
|
||||
@ -340,6 +340,17 @@ TypePointer Type::fromElementaryTypeName(string const& _name)
|
||||
}
|
||||
return ref->copyForLocation(location, true);
|
||||
}
|
||||
else if (t->category() == Type::Category::Address)
|
||||
{
|
||||
if (nameParts.size() == 2)
|
||||
{
|
||||
if (nameParts[1] == "payable")
|
||||
return make_shared<AddressType>(StateMutability::Payable);
|
||||
else
|
||||
solAssert(false, "Invalid state mutability for address type: " + nameParts[1]);
|
||||
}
|
||||
return make_shared<AddressType>(StateMutability::NonPayable);
|
||||
}
|
||||
else
|
||||
{
|
||||
solAssert(nameParts.size() == 1, "Storage location suffix only allowed for reference types");
|
||||
@ -439,20 +450,47 @@ MemberList::MemberMap Type::boundFunctions(Type const& _type, ContractDefinition
|
||||
return members;
|
||||
}
|
||||
|
||||
AddressType::AddressType(StateMutability _stateMutability):
|
||||
m_stateMutability(_stateMutability)
|
||||
{
|
||||
solAssert(m_stateMutability == StateMutability::Payable || m_stateMutability == StateMutability::NonPayable, "");
|
||||
}
|
||||
|
||||
string AddressType::richIdentifier() const
|
||||
{
|
||||
return "t_address";
|
||||
if (m_stateMutability == StateMutability::Payable)
|
||||
return "t_address_payable";
|
||||
else
|
||||
return "t_address";
|
||||
}
|
||||
|
||||
bool AddressType::isImplicitlyConvertibleTo(Type const& _other) const
|
||||
{
|
||||
if (_other.category() != category())
|
||||
return false;
|
||||
AddressType const& other = dynamic_cast<AddressType const&>(_other);
|
||||
|
||||
return other.m_stateMutability <= m_stateMutability;
|
||||
}
|
||||
|
||||
bool AddressType::isExplicitlyConvertibleTo(Type const& _convertTo) const
|
||||
{
|
||||
if (auto const* contractType = dynamic_cast<ContractType const*>(&_convertTo))
|
||||
return (m_stateMutability >= StateMutability::Payable) || !contractType->isPayable();
|
||||
return isImplicitlyConvertibleTo(_convertTo) ||
|
||||
_convertTo.category() == Category::Contract ||
|
||||
_convertTo.category() == Category::Integer ||
|
||||
(_convertTo.category() == Category::FixedBytes && 160 == dynamic_cast<FixedBytesType const&>(_convertTo).numBytes() * 8);
|
||||
}
|
||||
|
||||
string AddressType::toString(bool) const
|
||||
{
|
||||
if (m_stateMutability == StateMutability::Payable)
|
||||
return "address payable";
|
||||
else
|
||||
return "address";
|
||||
}
|
||||
|
||||
string AddressType::canonicalName() const
|
||||
{
|
||||
return "address";
|
||||
}
|
||||
@ -479,17 +517,29 @@ TypePointer AddressType::binaryOperatorResult(Token::Value _operator, TypePointe
|
||||
return Type::commonType(shared_from_this(), _other);
|
||||
}
|
||||
|
||||
bool AddressType::operator==(Type const& _other) const
|
||||
{
|
||||
if (_other.category() != category())
|
||||
return false;
|
||||
AddressType const& other = dynamic_cast<AddressType const&>(_other);
|
||||
return other.m_stateMutability == m_stateMutability;
|
||||
}
|
||||
|
||||
MemberList::MemberMap AddressType::nativeMembers(ContractDefinition const*) const
|
||||
{
|
||||
return {
|
||||
MemberList::MemberMap members = {
|
||||
{"balance", make_shared<IntegerType>(256)},
|
||||
{"call", make_shared<FunctionType>(strings{"bytes memory"}, strings{"bool", "bytes memory"}, FunctionType::Kind::BareCall, false, StateMutability::Payable)},
|
||||
{"callcode", make_shared<FunctionType>(strings{"bytes memory"}, strings{"bool", "bytes memory"}, FunctionType::Kind::BareCallCode, false, StateMutability::Payable)},
|
||||
{"delegatecall", make_shared<FunctionType>(strings{"bytes memory"}, strings{"bool", "bytes memory"}, FunctionType::Kind::BareDelegateCall, false)},
|
||||
{"send", make_shared<FunctionType>(strings{"uint"}, strings{"bool"}, FunctionType::Kind::Send)},
|
||||
{"staticcall", make_shared<FunctionType>(strings{"bytes memory"}, strings{"bool", "bytes memory"}, FunctionType::Kind::BareStaticCall, false, StateMutability::View)},
|
||||
{"transfer", make_shared<FunctionType>(strings{"uint"}, strings(), FunctionType::Kind::Transfer)}
|
||||
{"staticcall", make_shared<FunctionType>(strings{"bytes memory"}, strings{"bool", "bytes memory"}, FunctionType::Kind::BareStaticCall, false, StateMutability::View)}
|
||||
};
|
||||
if (m_stateMutability == StateMutability::Payable)
|
||||
{
|
||||
members.emplace_back(MemberList::Member{"send", make_shared<FunctionType>(strings{"uint"}, strings{"bool"}, FunctionType::Kind::Send)});
|
||||
members.emplace_back(MemberList::Member{"transfer", make_shared<FunctionType>(strings{"uint"}, strings(), FunctionType::Kind::Transfer)});
|
||||
}
|
||||
return members;
|
||||
}
|
||||
|
||||
namespace
|
||||
@ -1476,7 +1526,9 @@ bool ContractType::isImplicitlyConvertibleTo(Type const& _convertTo) const
|
||||
|
||||
bool ContractType::isExplicitlyConvertibleTo(Type const& _convertTo) const
|
||||
{
|
||||
return isImplicitlyConvertibleTo(_convertTo) || _convertTo.category() == Category::Address;
|
||||
if (auto const* addressType = dynamic_cast<AddressType const*>(&_convertTo))
|
||||
return isPayable() || (addressType->stateMutability() < StateMutability::Payable);
|
||||
return isImplicitlyConvertibleTo(_convertTo);
|
||||
}
|
||||
|
||||
bool ContractType::isPayable() const
|
||||
@ -3275,7 +3327,7 @@ MemberList::MemberMap MagicType::nativeMembers(ContractDefinition const*) const
|
||||
{
|
||||
case Kind::Block:
|
||||
return MemberList::MemberMap({
|
||||
{"coinbase", make_shared<AddressType>()},
|
||||
{"coinbase", make_shared<AddressType>(StateMutability::Payable)},
|
||||
{"timestamp", make_shared<IntegerType>(256)},
|
||||
{"blockhash", make_shared<FunctionType>(strings{"uint"}, strings{"bytes32"}, FunctionType::Kind::BlockHash, false, StateMutability::View)},
|
||||
{"difficulty", make_shared<IntegerType>(256)},
|
||||
@ -3284,7 +3336,7 @@ MemberList::MemberMap MagicType::nativeMembers(ContractDefinition const*) const
|
||||
});
|
||||
case Kind::Message:
|
||||
return MemberList::MemberMap({
|
||||
{"sender", make_shared<AddressType>()},
|
||||
{"sender", make_shared<AddressType>(StateMutability::Payable)},
|
||||
{"gas", make_shared<IntegerType>(256)},
|
||||
{"value", make_shared<IntegerType>(256)},
|
||||
{"data", make_shared<ArrayType>(DataLocation::CallData)},
|
||||
@ -3292,7 +3344,7 @@ MemberList::MemberMap MagicType::nativeMembers(ContractDefinition const*) const
|
||||
});
|
||||
case Kind::Transaction:
|
||||
return MemberList::MemberMap({
|
||||
{"origin", make_shared<AddressType>()},
|
||||
{"origin", make_shared<AddressType>(StateMutability::Payable)},
|
||||
{"gasprice", make_shared<IntegerType>(256)}
|
||||
});
|
||||
case Kind::ABI:
|
||||
|
@ -321,15 +321,16 @@ class AddressType: public Type
|
||||
public:
|
||||
virtual Category category() const override { return Category::Address; }
|
||||
|
||||
explicit AddressType()
|
||||
{
|
||||
}
|
||||
explicit AddressType(StateMutability _stateMutability);
|
||||
|
||||
virtual std::string richIdentifier() const override;
|
||||
virtual bool isImplicitlyConvertibleTo(Type const& _other) const override;
|
||||
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override;
|
||||
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
|
||||
virtual TypePointer binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const override;
|
||||
|
||||
virtual bool operator==(Type const& _other) const override;
|
||||
|
||||
virtual unsigned calldataEncodedSize(bool _padded = true) const override { return _padded ? 32 : 160 / 8; }
|
||||
virtual unsigned storageBytes() const override { return 160 / 8; }
|
||||
virtual bool isValueType() const override { return true; }
|
||||
@ -337,11 +338,17 @@ public:
|
||||
virtual MemberList::MemberMap nativeMembers(ContractDefinition const*) const override;
|
||||
|
||||
virtual std::string toString(bool _short) const override;
|
||||
virtual std::string canonicalName() const override;
|
||||
|
||||
virtual u256 literalValue(Literal const* _literal) const override;
|
||||
|
||||
virtual TypePointer encodingType() const override { return shared_from_this(); }
|
||||
virtual TypePointer interfaceType(bool) const override { return shared_from_this(); }
|
||||
|
||||
StateMutability stateMutability(void) const { return m_stateMutability; }
|
||||
|
||||
private:
|
||||
StateMutability m_stateMutability;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -755,7 +762,7 @@ public:
|
||||
{
|
||||
if (isSuper())
|
||||
return TypePointer{};
|
||||
return std::make_shared<AddressType>();
|
||||
return std::make_shared<AddressType>(isPayable() ? StateMutability::Payable : StateMutability::NonPayable);
|
||||
}
|
||||
virtual TypePointer interfaceType(bool _inLibrary) const override
|
||||
{
|
||||
|
@ -242,8 +242,14 @@ string ABIFunctions::cleanupFunction(Type const& _type, bool _revertOnFailure)
|
||||
break;
|
||||
}
|
||||
case Type::Category::Contract:
|
||||
templ("body", "cleaned := " + cleanupFunction(AddressType()) + "(value)");
|
||||
{
|
||||
AddressType addressType(dynamic_cast<ContractType const&>(_type).isPayable() ?
|
||||
StateMutability::Payable :
|
||||
StateMutability::NonPayable
|
||||
);
|
||||
templ("body", "cleaned := " + cleanupFunction(addressType) + "(value)");
|
||||
break;
|
||||
}
|
||||
case Type::Category::Enum:
|
||||
{
|
||||
size_t members = dynamic_cast<EnumType const&>(_type).numberOfMembers();
|
||||
|
@ -1259,7 +1259,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
|
||||
identifier = FunctionType(*function).externalIdentifier();
|
||||
else
|
||||
solAssert(false, "Contract member is neither variable nor function.");
|
||||
utils().convertType(type, AddressType(), true);
|
||||
utils().convertType(type, AddressType(type.isPayable() ? StateMutability::Payable : StateMutability::NonPayable), true);
|
||||
m_context << identifier;
|
||||
}
|
||||
else
|
||||
@ -1277,15 +1277,24 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
|
||||
{
|
||||
utils().convertType(
|
||||
*_memberAccess.expression().annotation().type,
|
||||
AddressType(),
|
||||
AddressType(StateMutability::NonPayable),
|
||||
true
|
||||
);
|
||||
m_context << Instruction::BALANCE;
|
||||
}
|
||||
else if ((set<string>{"send", "transfer", "call", "callcode", "delegatecall", "staticcall"}).count(member))
|
||||
else if ((set<string>{"send", "transfer"}).count(member))
|
||||
{
|
||||
solAssert(dynamic_cast<AddressType const&>(*_memberAccess.expression().annotation().type).stateMutability() == StateMutability::Payable, "");
|
||||
utils().convertType(
|
||||
*_memberAccess.expression().annotation().type,
|
||||
AddressType(),
|
||||
AddressType(StateMutability::Payable),
|
||||
true
|
||||
);
|
||||
}
|
||||
else if ((set<string>{"call", "callcode", "delegatecall", "staticcall"}).count(member))
|
||||
utils().convertType(
|
||||
*_memberAccess.expression().annotation().type,
|
||||
AddressType(StateMutability::NonPayable),
|
||||
true
|
||||
);
|
||||
else
|
||||
|
@ -0,0 +1,6 @@
|
||||
contract C {
|
||||
struct S {
|
||||
address payable a;
|
||||
address b;
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
contract C {
|
||||
address a;
|
||||
function f(address b) public pure returns (address c) {
|
||||
address d = b;
|
||||
return d;
|
||||
}
|
||||
}
|
7
test/libsolidity/syntaxTests/parsing/address_payable.sol
Normal file
7
test/libsolidity/syntaxTests/parsing/address_payable.sol
Normal file
@ -0,0 +1,7 @@
|
||||
contract C {
|
||||
address payable a;
|
||||
function f(address payable b) public pure returns (address payable c) {
|
||||
address payable d = b;
|
||||
return d;
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
contract C {
|
||||
function f(bytes memory b) public pure returns (address payable) {
|
||||
(address payable c) = abi.decode(b, (address));
|
||||
return c;
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
contract C {
|
||||
address constant a = address(0);
|
||||
address payable constant b = address(0);
|
||||
function f() public pure returns (address, address) {
|
||||
return (a,b);
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
contract C {
|
||||
address constant a = address(0);
|
||||
address payable constant b = address(0);
|
||||
function f() public {
|
||||
a = address(0);
|
||||
b = address(0);
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// TypeError: (129-130): Cannot assign to a constant variable.
|
||||
// TypeError: (153-154): Cannot assign to a constant variable.
|
@ -0,0 +1,11 @@
|
||||
contract A {
|
||||
struct S {
|
||||
address payable a;
|
||||
}
|
||||
S s;
|
||||
function f() public {
|
||||
s.a = address(this);
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// TypeError: (110-123): Type address is not implicitly convertible to expected type address payable.
|
@ -0,0 +1,20 @@
|
||||
contract A {
|
||||
struct S {
|
||||
address a;
|
||||
}
|
||||
S s;
|
||||
function f() public {
|
||||
s.a = address(this);
|
||||
}
|
||||
}
|
||||
contract B {
|
||||
struct S {
|
||||
address payable a;
|
||||
}
|
||||
S s;
|
||||
function f() public {
|
||||
s.a = address(this);
|
||||
}
|
||||
function() external payable {
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
contract C {
|
||||
function f(address a) public {
|
||||
selfdestruct(a);
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// TypeError: (69-70): Invalid type for argument in function call. Invalid implicit conversion from address to address payable requested.
|
@ -0,0 +1,7 @@
|
||||
contract C {
|
||||
function f(address) external pure {}
|
||||
function f(address payable) external pure {}
|
||||
|
||||
}
|
||||
// ----
|
||||
// TypeError: (58-102): Function overload clash during conversion to external types for arguments.
|
@ -0,0 +1,10 @@
|
||||
contract C {
|
||||
function f(address payable) internal pure {}
|
||||
function f(address) internal pure returns (uint) {}
|
||||
function g() internal pure {
|
||||
address a = address(0);
|
||||
uint b = f(a); // TODO: should this be valid?
|
||||
b;
|
||||
}
|
||||
}
|
||||
// ----
|
@ -0,0 +1,10 @@
|
||||
contract C {
|
||||
function f(address payable) internal pure {}
|
||||
function f(address) internal pure {}
|
||||
function g() internal pure {
|
||||
address payable a = address(0);
|
||||
f(a);
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// TypeError: (184-185): No unique declaration found after argument-dependent lookup.
|
@ -0,0 +1,11 @@
|
||||
contract C {
|
||||
function f() public pure {
|
||||
address payable[] memory a = new address payable[](4);
|
||||
address[] memory b = new address[](4);
|
||||
a = b;
|
||||
b = a;
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// TypeError: (166-167): Type address[] memory is not implicitly convertible to expected type address payable[] memory.
|
||||
// TypeError: (181-182): Type address payable[] memory is not implicitly convertible to expected type address[] memory.
|
@ -0,0 +1,7 @@
|
||||
contract C {
|
||||
function f(address) public pure {}
|
||||
function f(address payable) public pure {}
|
||||
|
||||
}
|
||||
// ----
|
||||
// TypeError: (56-98): Function overload clash during conversion to external types for arguments.
|
@ -0,0 +1,5 @@
|
||||
contract C {
|
||||
function f(address payable a) public {
|
||||
selfdestruct(a);
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
contract C {
|
||||
address payable[] a;
|
||||
address[] b;
|
||||
function f() public view {
|
||||
address payable[] storage c = a;
|
||||
address[] storage d = b;
|
||||
d = c; // TODO: this could be allowed in the future
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// TypeError: (172-173): Type address payable[] storage pointer is not implicitly convertible to expected type address[] storage pointer.
|
@ -0,0 +1,11 @@
|
||||
contract C {
|
||||
address payable[] a;
|
||||
address[] b;
|
||||
function f() public view {
|
||||
address payable[] storage c = a;
|
||||
address[] storage d = b;
|
||||
c = d;
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// TypeError: (172-173): Type address[] storage pointer is not implicitly convertible to expected type address payable[] storage pointer.
|
@ -0,0 +1,7 @@
|
||||
contract C {
|
||||
function f() public view {
|
||||
C c = address(2);
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// TypeError: (46-62): Type address payable is not implicitly convertible to expected type contract C.
|
@ -0,0 +1,8 @@
|
||||
contract C {
|
||||
function f() public pure returns (C c) {
|
||||
c = C(address(2));
|
||||
}
|
||||
function() external payable {
|
||||
}
|
||||
}
|
||||
// ----
|
@ -0,0 +1,10 @@
|
||||
contract C {
|
||||
function f(address a) public pure {
|
||||
address b;
|
||||
address payable c = a;
|
||||
c = b;
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// TypeError: (80-101): Type address is not implicitly convertible to expected type address payable.
|
||||
// TypeError: (115-116): Type address is not implicitly convertible to expected type address payable.
|
@ -0,0 +1,8 @@
|
||||
contract C {
|
||||
function f() public view returns (address payable a, address b) {
|
||||
(address c, address payable d) = (address(this), address(0));
|
||||
(a,b) = (c,d);
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// TypeError: (169-174): Type tuple(address,address payable) is not implicitly convertible to expected type tuple(address payable,address).
|
@ -0,0 +1,6 @@
|
||||
contract C {
|
||||
function f() public view returns (address payable a, address b) {
|
||||
(address c, address payable d) = (address(this), address(0));
|
||||
(a,b) = (d,c);
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
contract C {
|
||||
function f() public view {
|
||||
address payable a = address(this);
|
||||
a;
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// TypeError: (46-79): Type address is not implicitly convertible to expected type address payable.
|
@ -0,0 +1,10 @@
|
||||
contract C {
|
||||
function f() public view {
|
||||
address payable a = address(this);
|
||||
a;
|
||||
}
|
||||
function() external {
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// TypeError: (46-79): Type address is not implicitly convertible to expected type address payable.
|
@ -0,0 +1,9 @@
|
||||
contract C {
|
||||
function f() public view {
|
||||
address payable a = address(this);
|
||||
a;
|
||||
}
|
||||
function() external payable {
|
||||
}
|
||||
}
|
||||
// ----
|
@ -0,0 +1,10 @@
|
||||
contract C {
|
||||
function f() public view {
|
||||
address payable a = this;
|
||||
a;
|
||||
}
|
||||
function() external payable {
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// TypeError: (46-70): Type contract C is not implicitly convertible to expected type address payable.
|
@ -0,0 +1,8 @@
|
||||
contract C {
|
||||
function f() public pure {
|
||||
address a = address(0);
|
||||
a = address(1);
|
||||
address b = 0x0123456789012345678901234567890123456789;
|
||||
b = 0x9876543210987654321098765432109876543210;
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
contract C {
|
||||
function f() public pure {
|
||||
address payable a = address(0);
|
||||
a = address(1);
|
||||
address payable b = 0x0123456789012345678901234567890123456789;
|
||||
b = 0x9876543210987654321098765432109876543210;
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
contract C {
|
||||
function f() public pure returns (C c) {
|
||||
address a = address(2);
|
||||
c = C(a);
|
||||
}
|
||||
function() external payable {
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// TypeError: (92-96): Explicit type conversion not allowed from non-payable "address" to "contract C", which has a payable fallback function.
|
@ -0,0 +1,7 @@
|
||||
contract C {
|
||||
function f(address payable a) public pure {
|
||||
address payable b;
|
||||
address c = a;
|
||||
c = b;
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
contract C {
|
||||
function f() public view {
|
||||
C c = address(2);
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// TypeError: (46-62): Type address is not implicitly convertible to expected type contract C.
|
Loading…
Reference in New Issue
Block a user