0

This SO answer explains the differences between using a function marked with payable versus the contract implementing a receive function and then sending ETH directly to the contract: why contract must have a receive()/fallback() to receive ether? isn't a payable function enough?

My question is: when does it make more sense to use a function marked payable versus simply having a receive function? Both solve the same problem of allowing your contract to receive ETH, but I do not know what scenarios would favor implementing one over the other.

1 Answer 1

1

You're correct that both approaches accomplish the same goal of letting your contract increase its ETH balance. One is better than the other depending on the type of API you want users of your contract to have.

Below I'll explain where the receive function is best used, and where having a payable function is best.

tldr: The difference depends on whether the address interacting with your contract knows how your contract works (i.e. what public functions your contract exposes).

receive function:

In scenarios where you want a user to be able to send ETH to your contract, without any knowledge of how your contract works, a receive function is the way to go. The best example of this is a smart contract wallet like Gnosis Safe Wallet. If Alice wants to send funds to Bob's Safe Wallet which implement receive, all she has to do is call payable(bobs_safe_address).call{value: amount}(""). No matter what Gnosis does internally, Alice can easily send her funds to Bob in a single transaction and with no knowledge of the contract's internal workings.

If the Gnosis contract had a payable function instead of receive, then Alice would need to lookup what the function name and what its arguments are (if any), and then call that function while passing in the amount of ETH value she wants to send. This is more complicated than simply sending ETH in to the contract, and the simplicity of the receive is why it's preferred here.

payable function:

When your contract has a function that requires some non-zero ETH value in order to function correctly, this is where you should favor a payable function over a simple receive function.

An example of this would be if you were building a Charity contract, which exposes a function contribute() public payable function. Alice already knows about the functions exposed by the Charity contract, and knows she needs to call Charity.contribute in order to have her contribution correctly recorded by the contract. In this case marking the address as payable is the way to go. If the contract had the contribute function AND the receive function, or just a receive function that internally executed the contribute function, this would be a more confusing API because it's less clear to Alice what she needs to do in order to get her contribution correctly recorded.

The most ambiguous situation to decide between the two is when there is only one function that needs to be marked payable on your contract, and you're trying to decide whether to implement that function's logic as its owned named function (e.g. contribute) or to just use a receive function. In that case I think it's still best to use a named contribute function marked payable, because then you prevent users from unknowingly sending ETH to your contract when they didn't mean to.

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.