vyperlang / vyper

Pythonic Smart Contract Language for the EVM
https://vyperlang.org
Other
4.87k stars 799 forks source link

Interface type returns implicitly but not explicitly type `address` at assignment #3279

Open pcaversaccio opened 1 year ago

pcaversaccio commented 1 year ago

Version Information

What's your issue about?

Interface type returns implicitly but not explicitly type address at assignment.

Let me explain what I exactly mean. Let's start with the following snippet (let's call it contract.vy):

# @version 0.3.7
from vyper.interfaces import ERC20

asset: public(ERC20)

@external
def __init__(asset_: ERC20):
    self.asset = asset_

Using Titanoboa, we can simply do the following:

>>> import boa
>>> contract = boa.load("./contract.vy", "0xdAC17F958D2ee523a2206206994597C13D831ec7")
>>> contract.asset()
'0xdac17f958d2ee523a2206206994597c13d831ec7'

So the compiler returns implicitly the result of asset_.address at construction time. However, the compiler will throw with vyper.exceptions.TypeMismatch: Given reference has type ERC20, expected address if I make an explicit address type assignment:

# @version 0.3.7
from vyper.interfaces import ERC20

asset: public(address)

@external
def __init__(asset_: ERC20):
    self.asset = asset_

Ok, so far so good, but if I add a conversion to address it will throw with vyper.exceptions.TypeMismatch: Can't convert address to address

# @version 0.3.7
from vyper.interfaces import ERC20

asset: public(address)

@external
def __init__(asset_: ERC20):
    self.asset = convert(asset_, address)

So under the hood, the compiler already assigns the type address but does still not allow for an explicit address assignment, but requires the type ERC20 (or fix it via asset_.address). At least for me the feels kinda inconsistent.

How can it be fixed?

Allow for the following and the compiler automatically handles the assignment of asset_.address:

# @version 0.3.7
from vyper.interfaces import ERC20

asset: public(address)

@external
def __init__(asset_: ERC20):
    self.asset = asset_
pcaversaccio commented 1 year ago

Another example, where this behavior would be useful:

@external
def example_fn(asset_: ERC20):
    success: bool = empty(bool)
    return_data: Bytes[max_value(uint8)] = b""
    success, return_data = raw_call(asset_, ...)

To fix it currently, I must use .address again:

@external
def example_fn(asset_: ERC20):
    success: bool = empty(bool)
    return_data: Bytes[max_value(uint8)] = b""
    success, return_data = raw_call(asset_.address, ...)
charles-cooper commented 10 months ago

sorry i think i forgot to comment on this -- i think the issue here is the error message is confusing, but there are not implicit type conversions going on. there is no convert() role between interfaces and addresses, you just use <some contract>.address

pcaversaccio commented 10 months ago

sorry i think i forgot to comment on this -- i think the issue here is the error message is confusing, but there are not implicit type conversions going on. there is no convert() role between interfaces and addresses, you just use <some contract>.address

as per offline discussion, it's worth adding that the Vyper convention in the ABI encoding is to represent an interface as address. We should get the confusing error message fixed.