Continuing our exploration of the Uniswap V3 callback series, this lesson will demonstrate how to execute and repay flash borrows from V3 pools.
You should already know how V2 flash borrowing works. If you don’t, please go back and work through the entire “Smart Contract Arbitrage” series.
The execution for a V3 flash borrow is similar, but there are some important differences.
Unchanged
V3 flash borrows must be completed inside the callback function.
V3 flash borrows allow calldata to be passed through for evaluation inside the callback.
Changed
V3 flash borrows may not be repaid with an arbitrary mix of tokens. The pool sets the fee (or fees, if both tokens are borrowed).
V3 flash borrows are performed via a dedicated
flash
function, instead of a special mode internal to theswap
function.
Flash Callback
Reviewing the source of the V3 pool contract, we find that the flash
function performs the following action:
/// @inheritdoc IUniswapV3PoolActions
function flash(
address recipient,
uint256 amount0,
uint256 amount1,
bytes calldata data
) external override lock noDelegateCall {
uint128 _liquidity = liquidity;
require(_liquidity > 0, 'L');
uint256 fee0 = FullMath.mulDivRoundingUp(amount0, fee, 1e6);
uint256 fee1 = FullMath.mulDivRoundingUp(amount1, fee, 1e6);
uint256 balance0Before = balance0();
uint256 balance1Before = balance1();
if (amount0 > 0) TransferHelper.safeTransfer(token0, recipient, amount0);
if (amount1 > 0) TransferHelper.safeTransfer(token1, recipient, amount1);
IUniswapV3FlashCallback(msg.sender).uniswapV3FlashCallback(fee0, fee1, data);
uint256 balance0After = balance0();
uint256 balance1After = balance1();
require(balance0Before.add(fee0) <= balance0After, 'F0');
require(balance1Before.add(fee1) <= balance1After, 'F1');
[...]
The key line is IUniswapV3FlashCallback(msg.sender).uniswapV3FlashCallback(fee0, fee1, data);
which calls the uniswapV3FlashCallback
function at msg.sender
and then checks that the contract balance has increased by the appropriate amount. If not, it will revert.
What does uniswapV3FlashCallback
look like? That interface is defined in IUniswapV3FlashCallback.sol:
interface IUniswapV3FlashCallback {
/// @notice Called to `msg.sender` after transferring to the recipient from IUniswapV3Pool#flash.
/// @dev In the implementation you must repay the pool the tokens sent by flash plus the computed fee amounts.
/// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory.
/// @param fee0 The fee amount in token0 due to the pool by the end of the flash
/// @param fee1 The fee amount in token1 due to the pool by the end of the flash
/// @param data Any data passed through by the caller via the IUniswapV3PoolActions#flash call
function uniswapV3FlashCallback(
uint256 fee0,
uint256 fee1,
bytes calldata data
) external;
}
It’s a simple enough interface. The V3 pool contract will provide the fees owed for both tokens, plus pass through any calldata that was sent to flash
.
The uniswapV3FlashCallback
function in our smart contract must take care of all the necessary logic to repay the pool.
Contract Implementation
To test and demonstrate, let’s implement a simple flash callback in Vyper that simply repays the pool and returns.