-
-
Notifications
You must be signed in to change notification settings - Fork 804
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Bug: can't assert result of external calls to modifiable functions #1468
Comments
Doesn't seem to be call data causing the issue: >>> import vyper
>>> c_interface = vyper.compile_code("""
contract Foo:
def baz() -> bool: modifying
foo: Foo
@public
def __init__(_foo: address):
self.foo = Foo(_foo)
@public
def call_foo():
assert self.foo.baz()
""", output_formats=['abi', 'bytecode'])
>>> foo_interface = vyper.compile_code("""
@public
def baz() -> bool:
return True
""", output_formats=['abi', 'bytecode'])
>>> from web3 import Web3, EthereumTesterProvider
>>> w3 = Web3(EthereumTesterProvider())
>>> txn_hash = w3.eth.contract(**foo_interface).constructor().transact()
>>> address = w3.eth.waitForTransactionReceipt(txn_hash)['contractAddress']
>>> foo = w3.eth.contract(address, **foo_interface)
>>> txn_hash = w3.eth.contract(**c_interface).constructor(foo.address).transact()
>>> address = w3.eth.waitForTransactionReceipt(txn_hash)['contractAddress']
>>> contract = w3.eth.contract(address, **c_interface)
>>> contract.functions.call_foo().transact()
HexBytes('0x13583b7cb86379c320c2358df34a144e2e9f028aa1c8980af1ae14461f38deac') |
Same bug as #1377 |
I inspected the IR of both ways, and |
It's not just having to do with the semantics of contract Token:
def transferFrom(_from : address, _to : address, _value : uint256) -> bool: modifying
token: public(address)
@public
@constant
def passes() :
success: bool = Token(self.token).transferFrom(msg.sender, self, 37)
assert success |
Good find! |
Just wanna add that forcing all ERC20 transfers into two lines (catch return and assert) is:
I'm hoping there can be a more elegant solution the issues with assert-ing functions that can modify state. |
That's true but the ERC20 standard having a return code is pretty much a historical artifact of not having REVERT at the time. Nowadays everybody just REVERTs if the transfer preconditions are not met. |
Not asserting is not a solution here at all.
Historically Vyper has tried to produce clean, easily audit-able code that is as difficult to mess up as possible. This seems like a bit of a departure. Imo, if there are alternate solutions such as only preventing modifying state on asserting internal function calls its worth exploring. |
So I see a couple possibilities here
I don't think that rolling back VIP1150 in part as suggested will work.
This is a semantic inconsistency which can lead to even subtler bugs. For instance, |
I think requiring the handling of return values will assuage your concern. As a sort of opt-out behavior, we can just have a blank assignment if you really don't wait it, i.e. If the developer had a return call, they had it for a reason and you should probably handle it. |
The most common use case in Ethereum is ERC20 token transfers, and asserting |
Yes, so forcing developers to handle returns by setting to a local variable would solve the problem. The compiler could optimize it out if it's determined not to be necessary (but the opposite isn't true) |
yes but having to set a local variable is still significantly uglier and more verbose than just |
I don't disagree, but we have to balance with the issues we found from allowing state-modifying functions in assert statements e.g. #1150. This represents a compromise that, while a little annoying and uglier, improves security and also ensures devs are using other projects more effectively. It would actually be harder to misuse ERC20 in this case! We should turn this into a VIP if this is the path we want to take to solve this problem. |
Sure, but #1150 seems like a very rare/minor edge case which is still not that confusing (although I agree not ideal) whereas external token transfers are the most common thing on Ethereum. |
success: bool = token.transfer(...)
assert success seems clearer, more understandable, and in line with the values of readability than: assert token.transfer(...) Also forcing the handling of return statements makes this clearer, as the compiler wouldn't let you compile the following: token.transfer(...) # unhandled return value! |
If it's about aesthetics, the developer is entitled to write a beautifying function, def beautifulTransfer(tok, to, amount) :
ok: bool = tok.transfer(to, amount)
assert ok Or more generically, def assert_allow_modify(expr: bool, fail_message: bytes32 = "") :
ok: bool = expr
assert ok, fail_message And if there's demand, we could even put |
Adding my 2 wei, I very much like requiring functions that return a value to have that value assigned to a parameter and throwing a warning at the very least if that value isn't used. As @fubuloubu mentioned above, I'm also I'm a big fan of blank assignment to make explicit where something is not used |
Version Information
vyper --version
): 0.1.0b10What's your issue about?
Upgrading Uniswap from vyper 0.1.0b4 to 0.1.0b10 and noticed my tests were failing on asserting external contract calls that return bools.
This fails (reverts):
This succeeds:
The interface was defined as:
The text was updated successfully, but these errors were encountered: