A high-level technical overview and some questions to guide implementation
In August 2022, Leighton made a proposal in the Uniswap governance forum to “turn on the fee switch”. He framed the proposal as an experiment. Charging a protocol-level fee would directly impact liquidity provider economics. What effects would that have? Purposefully excluded from the proposal were questions about what the protocol DAO should do with its new revenue stream.
The proposal gained traction. Fees would be charged at the pool level, so there was substantive debate in the forums about which markets should be the subject of this experiment. Liquidity providers piped up and expressed their displeasure at the prospect, but ultimately opinion coalesced around three specific pools to test with the lowest possible protocol fee. While this conversation was happening, the proposal sailed through both a temperature check and a consensus check with overwhelming majorities.
“Turning on the fee switch” is phrase has been kicking around since Uniswap’s first deployment, and while the idea is simple in theory — the protocol should get paid a little for each trade it facilitates — in implementation it’s more complex than just pressing a button. In researching how Avantgarde should vote, I realised that I had no idea what the actual proposal would look like when it eventually got pushed onchain. The Uniswap docs are vague in this regard and I didn’t feel comfortable evaluating the downstream and second-order effects that a Yes vote might cause with incomplete information, so I went digging.
The results of my research are helpful in determining how I’ll vote when it eventually comes time to do so. They are also helpful in laying the groundwork for what happens after a successful experiment, and thinking about the work that would need to be done to roll out the fee switch for every market that Uniswap enables. I’ll recap below, starting with a quick primer on fees in general and then diving into protocol fees in particular.
A current overview of fees in Uniswap
A Uniswap pool is a smart contract that enables the exchange of one token for another. It’s trivial to create a pool, and anyone can do it by specifying three variables — the two tokens they want the pool to trade back and forth (let’s call them ABC and XYZ) and the fee, in basis points, that will be charged to do so (let’s say 30bps).
There are two types of potential user for every pool, Liquidity Providers and Swappers. Liquidity Providers deposit both tokens from the pair into the pool contract, allowing Swappers to exchange tokens from one side of the pair for tokens of the other. The Swapper pays the fee (in the token they’re selling) for the privilege, and the pool contract is smart enough to divvy the fee up pro rata between all liquidity providers who helped to make the trade happen.
Fees accrue in both of a pool’s tokens (assuming that there are trades in both directions) and are held separately from the liquidity provided to the pool. A Liquidity Provider can claim the fees they’re owed whenever they’d like, and send them wherever they like.
Taking the hypothetical ABC/XYZ pool above, I’ll walk through a transaction step by step:
Beginning Balances: Swapper: 2000 XYZ. Liquidity Provider: 10,000 ABC, 20,000 XYZ
- Spot ABCXYZ is 2. The Liquidity Provider deposits her 10,000 ABC and 20,000 XYZ to the pool in a single tick
- The Swapper sees the available liquidity and sells his 2000 XYZ to the pool. The pool reserves the fee in the amount of 6 XYZ (0.003 * 2000). The balance of the sell order (1994 ABC) gets executed at 2.00 spot, and the swapper receives 997 ABC (1994 / 2)
- The Liquidity Provider removes liquidity and claims fee. Fees can be claimed separately without removing liquidity, but this is useful in examining the impact of the fee on the total value of the Liquidity Provider’s holdings.
Ending Balances: Swapper: 997 ABC. Liquidity Provider: 9,003 ABC and 21,994 XYZ from LP, 6 XYZ from fee
Carving out a protocol fee
By default, Liquidity Providers currently capture all fees paid by Swappers in a pool. However, each pool has a function that, if called by Uniswap Governance (specifically, setFeeProtocol
, described here), slices off a fraction of every fee paid by a Swapper. This particular function is what has come to be known as the Fee Switch. The protocol fee is initialized to 0, but can be changed to any of the following values, each representing a percentage of the total fee: ¼, ⅕, ⅙, 1/7, ⅛, 1/9, or 1/10.
These slices are reserved for the protocol, and can only be claimed by Governance calling another function (collectProtocol
) on the pool contract. This would require another distinct vote. In other words, it is a separate discussion and decision to determine how the fees will be used — they are not just streaming into some amorphous “Uniswap Treasury” contract.
Expanding on the example above, I’ll show the impact on Liquidity Provider economics if the lowest protocol fee setting (1/10) was enabled:
Beginning Balances: Swapper: 0 ABC, 2000 XYZ. Liquidity Provider: 10,000 ABC, 20,000 XYZ, Uniswap: 0 ABC, 0 XYZ.
- Spot ABCXYZ is 2. The Liquidity Provider deposits her 10,000 ABC and 20,000 XYZ to the pool in a single tick
- The Swapper sees the available liquidity and sells his 2000 XYZ to the pool. The pool reserves the fee in the amount of 6 XYZ (0.003 * 2000). 1/10 of the fee (0.60 XYZ) is allocated to the protocol and the balance to the Liquidity Provider. The balance of the sell order (1994 ABC) gets executed at 2.00 spot, and the swapper receives 997 ABC (1994 / 2)
- Liquidity Provider removes liquidity and claims fee
- Uniswap Governance votes to claim fee from ABC/XYZ 30bps pool
Ending Balances: Swapper: 997 ABC, 0 XYZ. Liquidity Provider: 9,003 ABC and 21,994 XYZ from LP, 5.40 XYZ from fee. Uniswap: 0 ABC, 0.60 XYZ
This design is what makes Leighton’s experiment so powerful. Pressing the fee switch button (I guess it is actually that easy) does one thing and one thing only. It changes the breakdown of fees in a particular liquidity pool. Any fees generated for the Protocol will remain totally inaccessible to the DAO without further work or votes. This is what allows us to witness the first-order effects of the fee switch (will Liquidity Providers flee en masse? Will spreads widen beyond repair?) without having to worry about most of the second order effects (What are we going to do with all this money????).
Looking Forward
In the event this experiment is successful, there will be some interesting technical problems to solve in the service of rolling out fees across the protocol. As we progress along that path, I’ll be thinking about the following:
- There are a huge number of Uniswap pool contracts in existence. I haven’t done the research, but I would wager that the vast majority of them do zero volume and spending the gas to change the setting would cost more than any potential future revenue. Should we whittle down the list of existing pools that collect a protocol fee? And going forward, how does governance change the pool deployer so that new pools default to a 1/10 protocol fee? (This is presumably fairly straightforward though I couldn’t find it in a quick scan of the contracts)
- How do you collect fees from dozens, hundreds or eventually thousands of pools in a scalable manner? Each pool has its own distinct function to call. Assuming that fees would ideally be collected on a regular basis leads to the conclusion the current architecture would require numerous and frequent governance votes to collect fees, which would likely lead to extreme voter fatigue and eventually apathy.
- How do you manage price exposure to the long tail of shitcoins? Should fees be collected directly in token form a la Paraswap or converted to USDC (or similar) upon collection a la 1Inch?
- Where do the fees go? The
collectProtocol
function allows the caller to specify an address that will receive the collected balances. History has shown that sticking them in the Timelock and letting Governance decide how to spend them is suboptimal. How would these funds be managed transparently and trustlessly?
I’m particularly looking forward to the Alastor report that the UF commissioned. Having chatted with the Jordan and Sam, they are building powerful, data-driven insights about how the protocol fee might change user behavior for the better. No spoilers here, but their initial findings and recommendations were not what I expected.
Uniswap was one of the very first integrations our team built for Enzyme (then Melon). It is a foundational protocol, and I am honored to be involved in helping to steer its future development both through Avantgarde’s work as a delegate and through our future cooperation with the Uniswap Foundation.