Dev Update #4
A few weeks have passed since our last post, so high time for a new Developer Update!
In this update we will go over a few development highlights from last month:
- Migration of our testnet application from Rinkeby to Goerli.
- Integration between Uniswap V2 and the active asset manager module.
- A new pricing module for Uniswap V2 LP-tokens — and how protocols have gotten exploited in the past by using bad implementations.
Before we dive into these topics, we’re incredibly excited to announce that one of our first investors, and someone with whom we’ve been working closely ever since, decided to join the Arcadia Finance team. Packed with experience from previous roles at start-ups and VC, and being an ex-founder himself, he will lead our business efforts. Stay tuned for his upcoming blog post.
Ok, time to get technical.
With the successful merge last September, a new era for the Ethereum network began. While this is a big step forward for the ecosystem, it also means the end of the Rinkeby testnet — the blockchain network where we, as early blockchain developers, deployed our first snippets of solidity back in 2016, and were Arcadia’s smart contracts were initially deployed.
In preparation for the depreciation of the Rinkeby testnet, we deployed all Arcadia’s contracts on the Goerli testnet, and got our hands into a fat stack of GoerliETH to replenish our faucet.
Hint: our faucet drips more than any other Goerli faucet!
You can now play around our test-application via https://app.arcadia.finance/.
Active Asset Management Module
As you are all hopefully aware one of the neat features of Arcadia Vaults is the possibility to actively manage your assets, even if they are used as collateral to back open positions. This month we formalized the asset manager module and implemented a first integration: swapping assets via Uniswap V2.
Through Arcadia, users with assets deposited in one of our Vaults can actively manage those assets in a single click and in a gas-efficient manner. In the past, users had to jump through hoops to de-risk (or boost) their positions. For example, users with assets deposited in a vault looking to de-risk their position (or the opposite), needed to first close their financial position, withdraw assets, swap assets, deposit assets and then re-take their position. This process was extremely cumbersome and expensive. Not anymore. Arcadia’s active asset management module allows all of this to be done in a single click, and in a much more gas-efficient manner. Never again users have to hassle around with multiple transactions.
We’re already working on the next features of Arcadia’s active asset management module. These will include withdrawing and adding liquidity to a Uniswap V2 pool, as well as changing the liquidity ranges of Uniswap V3 positions. More on that in a later update.
Uniswap V2 Pricing Module
With Arcadia Vaults, we are building the non-custodial on-chain margin account for DeFi. Our Vaults are token standard agnostic, able to handle any token implementation. The only thing that matters is that the underlying asset itself is of quality (sufficient on-chain liquidity, acceptable historic price volatility, a track record of operating securely, have been audited, etc) to serve as collateral. As such, our users should be able to use their LP-tokens as collateral as long as the two tokens of the pair are of quality and individually allowed in our protocol.
This month we finished the implementation of a pricing module for Uniswap V2 LP tokens. A Uniswap V2 LP token is what we call a derivative token. It will not have a separate price oracle per Liquidity Pool. Instead, we will first derive the amount of underlying tokens for which we do have a price feed. Next, the total value of the LP token is calculated based on the underlying token amounts.
Let’s dive a bit deeper into how this pricing module works.
How NOT to do the implementation
A naïve implementation would be to first fetch 4 values from the smart contract of the Liquidity Pool (LP):
- The balance of the LP token in the Vault (the number of shares of the LP owned by our Vault): liquidityAmount
- The total amount of outstanding shares of the LP: totalSupply
- The total amount of token0 in the LP: reserve0
- The total amount of token1 in the LP: reserve1
The amount of underlying tokens can then be calculated as:
And the total value of the LP token as:
This type of implementation opens the door for flashloan attacks. We will outline below how an atacker would take advantage of this naive implementation.
The implementation we described above is not flashloan-resistant. It depends entirely on the ratio of the reserves of the tokens in the Liquidity Pool, which is something an attacker can easily manipulate.
For example, suppose the following situation where the liquidity pool has the following initial state:
- tokenA is worth 1USD
- The amount of tokenA in the pool (reserveA) is 900 000)
- tokenB is worth 10USD
- The amount of tokenB in the pool (reserveB) is 90 000)
- totalSupply is currently 9
→ The pool is currently balanced (there is for $900 000 of tokenA in the pool and for $900 000 of tokenB)
Our attacker takes a flashloan of 100 000 tokenA and 1 010 000 tokenB and does the following two operations:
1) The attacker deposits 100 000 tokenA and 10 000 tokenB in the pool and now owns 10% of its liquidity. The state of the Liquidity Pool:
- reserveA is 1 000 000
- reserveB is 100 000
- totalSupply is currently 10
- attacker owns 1 share (10% of the totalSupply)
- Using our previous naïve pricing implementation, the attackers LP-token has a value of $200 000
2) The attacker uses the remaining 1 000 000 tokenB of the flashloan to bring the pool out of balance by swapping 1 000 000 tokenB for tokenA.
The amount of tokenA can be calculated for a Uniswap V2 pool as follows:
The state of the Liquidity Pool after the swap:
- reserveA is 91 157
- reserveB is 1 100 000
- totalSupply is currently 10
- attacker owns 1 share (10% of the totalSupply)
- Using our previous naïve pricing implementation, the attackers LP-token has a value of $1 109 115
By bringing the pool out of balance, the attacker’s LP-token is now priced at $1 109 115 instead of its real value of $200 000.
Since our attacker’s LP-token is valued at $1 109 115, he can use it as collateral and take a loan of $1 000 000.
Finally the attacker swaps back the tokenA for 997 000 tokenB, repays the flashloan and walks away with a profit of almost $800 000.
This naïve implementation of pricing Uniswap V2 tokens is clearly unsafe and relying on LP reserves has resulted in a number of exploits in the past.
Arcadia’s implementation: Flashloan resistant pricing logic
Before we value an LP-token, we do a check if the pool is balanced: the ratio of the prices of both tokens should be equal to the inverse ratio of the reserves:
But what should the pricing logic be when the pool is unbalanced (i.e., the equation doesn’t hold)? Reverting is not the solution, a malicious actor could abuse this to censor Vaults from being liquidated. Returning a value of 0 is also not a good solution, malicious actors could bring pools out of balance on purpose and liquidate Vaults that are now valued at 0 because they use the LP-token as collateral.
A first step in deriving the correct value of a LP-pool out of equilibrium is to realize that pools out of equilibrium generate an opportunity for arbitrageurs to make a profit. They could buy token0 on a different market, swap it to token1 in our unbalanced pool and sell token1 again on a different market. They could do this as long as the pool remains unbalanced and only stop when the pool reaches equilibrium.
In other words, the solution to the question of, what should the pricing logic be when the pool is unbalanced, is to calculate the reserves of the pool after a profit-maximizing arbitrage trade is executed. A full derivation of this profit-maximizing arbitrage trade, given a fixed external trusted price for both tokens, can be found here https://arxiv.org/pdf/1911.03380.pdf.
For readers that don’t like all those Greek letters, we will give a quick summary: the profit-maximizing trade can either be to swap token0 for token1, or the inverse. This will depend on which direction the pool is unbalanced. In the formulas below we will use the notation tokenIn and tokenOut for -as the names suggest- the tokens that come into the pool and those that go out of the pool.
An arbitrageurs profit, therefore, is given as:
This is the cost function we will maximize. We have the following constraints:
Lastly, we have the relation between amountIn and amountOut of a Uniswap V2 LP:
We can solve this optimization problem for amountIn and derive amountOut from the Uniswap V2 AMM equation above:
Now that we have amountIn and amountOut, we can finally calculate the corrected reserves:
And finally we use the corrected reserves to calculate the total value of the LP-token:
Take note Pancakebunny, Spartan, Xtoken…
That wraps it up for this month’s post. Next month we will bring you more math around Uni V3 positions, risk models and some other exiting stuff we’re working on. Stay tuned!
If you want to learn more about how we’re improving on the technical limitations of current protocols, follow us on twitter. If you’re interested in building with us, please reach out.