Add payable and non-payable state mutability to AddressType.

This commit is contained in:
Daniel Kirchner 2018-09-05 17:59:55 +02:00
parent 9214c7c34f
commit 12aaca1645
47 changed files with 468 additions and 82 deletions

View File

@ -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.

View File

@ -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 {

View File

@ -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 {

View File

@ -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);

View File

@ -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,

View File

@ -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;

View File

@ -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
{

View File

@ -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);

View File

@ -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.

View File

@ -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))
})
{

View File

@ -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;

View File

@ -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

View File

@ -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:

View File

@ -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
{

View File

@ -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();

View File

@ -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

View File

@ -0,0 +1,6 @@
contract C {
struct S {
address payable a;
address b;
}
}

View File

@ -0,0 +1,7 @@
contract C {
address a;
function f(address b) public pure returns (address c) {
address d = b;
return d;
}
}

View 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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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.

View File

@ -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.

View File

@ -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 {
}
}

View File

@ -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.

View File

@ -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.

View File

@ -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;
}
}
// ----

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -0,0 +1,5 @@
contract C {
function f(address payable a) public {
selfdestruct(a);
}
}

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -0,0 +1,8 @@
contract C {
function f() public pure returns (C c) {
c = C(address(2));
}
function() external payable {
}
}
// ----

View File

@ -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.

View File

@ -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).

View File

@ -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);
}
}

View File

@ -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.

View File

@ -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.

View File

@ -0,0 +1,9 @@
contract C {
function f() public view {
address payable a = address(this);
a;
}
function() external payable {
}
}
// ----

View File

@ -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.

View File

@ -0,0 +1,8 @@
contract C {
function f() public pure {
address a = address(0);
a = address(1);
address b = 0x0123456789012345678901234567890123456789;
b = 0x9876543210987654321098765432109876543210;
}
}

View File

@ -0,0 +1,8 @@
contract C {
function f() public pure {
address payable a = address(0);
a = address(1);
address payable b = 0x0123456789012345678901234567890123456789;
b = 0x9876543210987654321098765432109876543210;
}
}

View File

@ -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.

View File

@ -0,0 +1,7 @@
contract C {
function f(address payable a) public pure {
address payable b;
address c = a;
c = b;
}
}

View File

@ -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.