Call{value:} results in an internal transaction — so why is it recommended over tranfer()?

Working on a contract that should allow users to withdraw funds. I've read a number of sources saying that call{value:} (formerly call.value()) should be used over transfer() , e.g.,

However, when I use it to withdraw funds in the ropsten contract, it results in an "internal transaction" for the amount withdrawn, and the metamask wallet registers it as a successful withdrawal of 0.0 ETH, rather than the amount listed, in the internal transaction, as having been withdrawn.

I've now read enough about internal transactions to understand that they are not actually recorded on the blockchain — or at least not in the ordinary way (?) — so I'm wondering why call{value:} (with the proper code to prevent reentrancy) is regarded as the solution to the problem of transer() being dependent on gas prices.

internal transactions are not "real" transactions, it's how etherscan (and forks) represents calls between contracts. transfer() is just syntactic sugar to call{gas:2300}() (as explained in the consensys blog).

call{}() passes all available gas instead. this means that the contract receiving the ETH will have enough gas to do a reentrancy attack, this is why a reentrancy guard is needed in this case (this also future proofs the contract against gas changes).

the value field in the metamask transaction is the value sent in that transaction to trigger the transfer() , not the amount received.

Receiving a transaction sent from another EOA account is different from calling a contract that sends eth. in the former is a transaction and metamask shows it as a received tx, the latter is an internal transaction and metamask shows it as a contract call)

2 Likes

magnificent answer.

the highlights, for me:

"transfer() is just syntactic sugar to call{gas:2300}()" — i missed this on the consensys blog.

"the value field in the metamask transaction is the value sent in that transaction to trigger the transfer() , not the amount received." — this was the most useful clarification for dealing with my particular confusion.

"Receiving a transaction sent from another EOA account is different from calling a contract that sends eth. in the former is a transaction and metamask shows it as a received tx, the latter is an internal transaction and metamask shows it as a contract call"