We got a task recently to develop a ERC20 token, which takes ETH fees on transfer. One of our developers created a prototype in a couple of days, but was struggling with some edge-case they've caught in tests.
The problem looked like a revert somewhere in a Uniswap code
Error: VM Exception while processing transaction: reverted with reason string 'UniswapV2: TRANSFER_FAILED'
Pretty annoying. Hardhat does not provide enough information about the stack trace. Reading code didn't help. But thankfully now we have tenderly forks! After changing RPC to the fork, we found this:
Imporant thing here are lines 330–331 (and a comment). What is shows, that the contract computes amount of token which were sent to it as a difference between known reserves and a balance. The problem is, that reserves are updated at the end of transaction. So you have to do some dark magic with balances. So we did that. We _minted some tokens before swap and _burned them afterwards. But it didn't help either. Now we are having a reentrancy lock. WHY?! Why on Earth it is OK on selling and not OK on buying? Let's have a look at the code again.
This is how you sell token. You transfer it to the contract, then call _swapSupportingFeeOnTransferToken. Inside of _swapSupportingFeeOnTransferToken, it transfers the out token to the receiver.
So, it means that when you sell the token, it calls _swapSupportingFeeOnTransferToken on transfer, and then _swapSupportingFeeOnTransferToken once again.
But when you are buying tokens things are different.
Well, not really. It is symmetrical. But our token now is transferred to the recipient from the inside of _swapSupportingFeeOnTransferToken, so now we are getting a reentrancy lock protection error.
The solution we are proposing to the customer right now is accumulating tokens on a contract on buys and performing swaps on sells.