TLDR: Uniswap often requires too much ETH to be sent for a swap, returning spare ETH back to the user. For swaps into thin liquidity this change is sometimes not refunded. Radix’s “buckets” - temporary containers for tokens - make fixing this bug elegant and simple.
The incident in question, courtesy of @0x534154.
Uniswap is the leading Automated Market Maker (AMM) on Ethereum. It allows users to swap between pairs of tokens held by “Liquidity Pools” (LPs). If a user wishes to swap two tokens, they use the Uniswap website to sign a tx which calls the Uniswap Router contract. This contract then calls other downstream contracts, such as the relevant LP contract for that particular token pair.
When swapping ETH, users must first send their ETH to the Uniswap Router contract. This is because ETH behaves differently to all other tokens on Ethereum, such as ERC20 tokens. Uniswap offers two methods for swapping tokens. You can either swap from an exact amount and receive a variable amount as calculated during the transaction (tx): exactInput; or you can swap a variable amount to get a fixed amount of token back: exactOutput.
For exactOutput tx’s, as the input is variable, users commonly send the router too much ETH. Any excess ETH that was not used for the swap must then be refunded.
In the example above, the user has sent the router 6 ETH. The LP executes the swap to obtain 29k RDNT for 5 ETH. 1 ETH is now due to be refunded to the user, and the refundETH method refunds all ETH remaining in the router back to the user (the 1 ETH).
However, Uniswap is only configured to call the refundETH method for exactOutput tx. For exactInput tx, refundETH is not called.
This shouldn’t normally be a problem, as exact Input transactions should not be required to give any change, as the output is variable. But in some specific cases, if the LP is short of liquidity, not all of the ETH sent is used for the swap. And because this is an exactInput tx, the refundETH method is not called, and the excess ETH is not refunded.
This is exactly what happened to some users who used Uniswap on Arbirtrum on July 25 2022.
One user who wished to swap 13.85 ETH for RDNT only received 14.7k RDNT. They should have been refunded 11.31 ETH.
That 11.31 ETH was held by the router until the next user performed an exactOutput tx, which called the refundETH method, and incorrectly refunded the 11.31 ETH to that next user.
So why do “buckets” on Radix’s upcoming Babylon release make fixing this bug a breeze?
Let’s first revisit Radix’s asset-oriented model for DeFi.
All assets, such as tokens, are called resources. You can think of a resource as like a physical object that has to be somewhere at all times.
XRD, Radix’s native token, is a resource. So too would ETH and RDNT if they were on Radix. In fact, XRD is implemented in exactly the same way as all other tokens - developers don’t have to code different logic for XRD or any other token. Because resources behave like physical objects, they have to be somewhere. There are two places they can be: vaults, for permanent storage; or buckets, a temporary container for when resources are passed between vaults.
Vaults live inside smart contract components. All Radix users will have at least one “account component” on-ledger that holds the vaults for their tokens.
Buckets are transient, existing only during a tx to transport resources. All buckets must be empty of resources and resolved by the end of a tx, or it fails. If we recreated the exactInput tx above on Radix, you can see that tokens are passed between vaults in buckets, via the tx. After the swap, we still have the spare 11.31 ETH. But in this case, it is returned to the tx in a bucket, and then back to the user.
If you’re a Scrypto developer building on Radix, you don’t need to worry about calculating how much change to send back and building a specific method for it - you just return the bucket containing what’s left over.
To learn more about Scrypto’s asset-oriented paradigm, this article provides a great overview.
For the last in the Rekt Retweet series: Rekt Retweet #12: Native Token Validation - Why the $2.1m Gym Network hack could NEVER happen on Radix