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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion crates/sema/src/hir/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! High-level intermediate representation (HIR).

use crate::builtins::Builtin;
use crate::{builtins::Builtin, ty::Gcx};
use derive_more::derive::From;
use either::Either;
use rayon::prelude::*;
Expand Down Expand Up @@ -744,6 +744,28 @@ impl Contract<'_> {
}
}

/// Returns `true` if the contract can receive ether.
///
/// A contract can receive ether if it has:
/// - A `receive()` function, OR
/// - A `fallback()` function with `payable` state mutability
pub fn can_receive_ether(contract: &Contract<'_>, gcx: Gcx<'_>) -> bool {
// Check if contract has receive function
if contract.receive.is_some() {
return true;
}

// Check if contract has payable fallback function
if let Some(fallback_id) = contract.fallback {
let fallback = gcx.hir.function(fallback_id);
if fallback.state_mutability == StateMutability::Payable {
return true;
}
}

false
}

/// A modifier or base class call.
#[derive(Clone, Copy, Debug)]
pub struct Modifier<'hir> {
Expand Down
46 changes: 46 additions & 0 deletions crates/sema/src/ty/ty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ pub enum TyConvertError {

/// Invalid conversion between types.
InvalidConversion,

/// Contract cannot be converted to address payable because it cannot receive ether.
ContractNotPayable,

/// Non-payable address cannot be converted to a contract that can receive ether.
AddressNotPayable,
}

impl TyConvertError {
Expand All @@ -54,6 +60,18 @@ impl TyConvertError {
Self::Incompatible => {
format!("expected `{}`, found `{}`", to.display(gcx), from.display(gcx))
}
Self::ContractNotPayable => {
format!(
"cannot convert `{}` to `address payable` because it has no receive function or payable fallback",
from.display(gcx)
)
}
Self::AddressNotPayable => {
format!(
"cannot convert non-payable `address` to `{}` because it has a receive function or payable fallback",
to.display(gcx)
)
}
}
}
}
Expand Down Expand Up @@ -604,6 +622,34 @@ impl<'gcx> Ty<'gcx> {
}
}

// Contract -> address (always allowed)
(Contract(_), Elementary(Address(false))) => Ok(()),

// Contract -> address payable (only if contract can receive ether)
(Contract(contract_id), Elementary(Address(true))) => {
let contract = gcx.hir.contract(contract_id);

if hir::can_receive_ether(contract, gcx) {
Ok(())
} else {
Result::Err(TyConvertError::ContractNotPayable)
}
}

// Address payable -> Contract (always allowed)
(Elementary(Address(true)), Contract(_)) => Ok(()),

// Address (non-payable) -> Contract (only if contract cannot receive ether)
(Elementary(Address(false)), Contract(contract_id)) => {
let contract = gcx.hir.contract(contract_id);

if hir::can_receive_ether(contract, gcx) {
Result::Err(TyConvertError::AddressNotPayable)
} else {
Ok(())
}
}

_ => Result::Err(TyConvertError::InvalidConversion),
}
}
Expand Down
15 changes: 13 additions & 2 deletions crates/sema/src/typeck/checker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -394,8 +394,19 @@ impl<'gcx> TypeChecker<'gcx> {
}
}
hir::ExprKind::Payable(expr) => {
let ty = self.expect_ty(expr, self.gcx.types.address);
if ty.references_error() { ty } else { self.gcx.types.address_payable }
let ty = self.check_expr(expr);
if ty.references_error() {
return ty;
}

let target_ty = self.gcx.types.address_payable;
let Err(err) = ty.try_convert_explicit_to(target_ty, self.gcx) else {
return target_ty;
};

let mut diag = self.dcx().err("invalid explicit type conversion").span(expr.span);
diag = diag.span_label(expr.span, err.message(ty, target_ty, self.gcx));
self.gcx.mk_ty_err(diag.emit())
}
hir::ExprKind::Ternary(cond, true_, false_) => {
let _ = self.expect_ty(cond, self.gcx.types.bool);
Expand Down
57 changes: 57 additions & 0 deletions tests/ui/typeck/contract_address_conversions_explicit.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
//@compile-flags: -Ztypeck

contract NoReceive {}

contract WithReceive {
receive() external payable {}
}

contract WithPayableFallback {
fallback() external payable {}
}

contract WithNonPayableFallback {
fallback() external {}
}

contract Test {
function testContractToAddress(
NoReceive c1,
WithReceive c2,
WithPayableFallback c3
) public pure {
// All contracts can convert to address
address a1 = address(c1);
address a2 = address(c2);
address a3 = address(c3);
}

function testContractToAddressPayable(
NoReceive c1,
WithReceive c2,
WithPayableFallback c3,
WithNonPayableFallback c4
) public pure {
// Only contracts with receive or payable fallback can convert to address payable
address payable p1 = payable(c1); //~ ERROR: invalid explicit type conversion
address payable p2 = payable(c2); // ok
address payable p3 = payable(c3); // ok
address payable p4 = payable(c4); //~ ERROR: invalid explicit type conversion
}

function testAddressToContract(address addr, address payable paddr) public pure {
// Non-payable address can convert to contracts without receive/payable fallback
NoReceive c1 = NoReceive(addr); // ok
WithNonPayableFallback c4 = WithNonPayableFallback(addr); // ok

// Non-payable address CANNOT convert to contracts with receive/payable fallback
WithReceive c2 = WithReceive(addr); //~ ERROR: invalid explicit type conversion
WithPayableFallback c3 = WithPayableFallback(addr); //~ ERROR: invalid explicit type conversion

// Payable address can convert to any contract
WithReceive c5 = WithReceive(paddr); // ok
WithPayableFallback c6 = WithPayableFallback(paddr); // ok
NoReceive c7 = NoReceive(paddr); // ok
WithNonPayableFallback c8 = WithNonPayableFallback(paddr); // ok
}
}
26 changes: 26 additions & 0 deletions tests/ui/typeck/contract_address_conversions_explicit.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
error: invalid explicit type conversion
╭▸ ROOT/tests/ui/typeck/contract_address_conversions_explicit.sol:LL:CC
LL │ address payable p1 = payable(c1);
╰╴ ━━ cannot convert `contract NoReceive` to `address payable` because it has no receive function or payable fallback

error: invalid explicit type conversion
╭▸ ROOT/tests/ui/typeck/contract_address_conversions_explicit.sol:LL:CC
LL │ address payable p4 = payable(c4);
╰╴ ━━ cannot convert `contract WithNonPayableFallback` to `address payable` because it has no receive function or payable fallback

error: invalid explicit type conversion
╭▸ ROOT/tests/ui/typeck/contract_address_conversions_explicit.sol:LL:CC
LL │ WithReceive c2 = WithReceive(addr);
╰╴ ━━━━━━━━━━━━━━━━━ cannot convert non-payable `address` to `contract WithReceive` because it has a receive function or payable fallback

error: invalid explicit type conversion
╭▸ ROOT/tests/ui/typeck/contract_address_conversions_explicit.sol:LL:CC
LL │ WithPayableFallback c3 = WithPayableFallback(addr);
╰╴ ━━━━━━━━━━━━━━━━━━━━━━━━━ cannot convert non-payable `address` to `contract WithPayableFallback` because it has a receive function or payable fallback

error: aborting due to 4 previous errors

Loading