# YEARN AND L2 ROLLUPS: MAXIMIZING YIELD We are aware that liquidity in Layer 2 (L2) decentralized exchanges (DEXes) is not as substantial as Ethereum and is often fragmented. At the time of writing, there are numerous DEXes where liquidity is dispersed among them. ## Optimism and popular liquidity rewards liquidity In Optimism, the primary rewards is OP. At the time of writing there are 3 major liquidity sources such as UniswapV3, Sushiswap and Velodrome (Solidly fork) ### Liquidity for OP Velodrome Finance OP pools ![](https://i.imgur.com/ChLOehm.png) Sushiswap OP pool ![](https://i.imgur.com/bjhF8dh.png) UniswapV3 OP pools ![](https://i.imgur.com/hYeGOHR.png) Seems like the biggest liquidity source for OP token is the UniswapV3 and Velodrome after it. ## Optimism and popular stable coin liquidity 1. **USDC** UniswapV3 liquidity for USDC ![](https://i.imgur.com/sOEIZTz.png) Sushiswap liquidity for USDC ![](https://i.imgur.com/nEN5HBk.png) Velodrome liquidity for USDC ![](https://i.imgur.com/HVuHjPA.png) 2. **DAI** Velodrome liquidity for DAI ![](https://i.imgur.com/XU3s5Tp.png) Uniswap V3 liquidity for DAI ![](https://i.imgur.com/YzmWcWz.png) 3. **sUSD** Sushiswap liquidity for sUSD ![](https://i.imgur.com/tvqKfCv.png) UniswapV3 liquidity for sUSD ![](https://i.imgur.com/FQiDAir.png) Velodrome liquidity for sUSD ![](https://i.imgur.com/gxameW7.png) It is observable that the biggest liquidity sources are Velodrome and UniswapV3 and sushiswap is just an alternative to them. ## What can we do to optimise swaps? ### Can we introduce trade factory to these chains? In an ideal scenario, we should be harvesting rewards more frequently on these chains since gas prices are much lower than on the mainnet. However, this requires **more human interaction** to trigger the swaps. It would not be ideal or efficient for Yearn to manually sell rewards using the Trade Factory like this, as it would be time-consuming and not scalable. ### A potential solution, new functionality for strategies to use an aggregator for swapping ``` // SPDX-License-Identifier: MIT pragma solidity 0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; contract Strategy { mapping(address => address) public rewardTokenToTargetToken; address public immutable ZERO_X_ROUTER; // errors error LengthDismatches(); error ZeroAddress(); error WrongOutputToken(); error WrongInputToken(); error WrongInputTokenAmount(); error ZeroOutputTokenAmount(); error InvalidSwapData(); error PartialSwap(); constructor( address[] memory initialRewardTokens, address[] memory initialTargetTokens, address _ZERO_X_ROUTER ) { if (initialRewardTokens.length != initialTargetTokens.length) { revert LengthDismatches(); } // Set the 0x router as IMMUTABLE, no rug ZERO_X_ROUTER = _ZERO_X_ROUTER; // Set the underlying rewards and the targets for it // If this is a curve strategy we can set it in the constructor by looping the coins() function for (uint i = 0; i < initialRewardTokens.length; ++i) { if ( initialRewardTokens[i] == address(0) || initialTargetTokens[i] == address(0) ) { revert ZeroAddress(); } rewardTokenToTargetToken[initialRewardTokens[i]] = initialTargetTokens[i]; } } /// @dev Defines a transformation to run in `transformERC20()`. // We can put this else where I am just playing around struct Transformation { // The deployment nonce for the transformer. // The address of the transformer contract will be derived from this // value. uint32 deploymentNonce; // Arbitrary data to pass to the transformer. bytes data; } function claimableRewards() public view returns (uint256 claimableReward) {} function swap(bytes calldata swapData) external { if (swapData[0] == 0x41) { // MUST call `transformERC20()`, there are no other function starts with 0x41 (IERC20 input, IERC20 output, uint256 inputTokenAmount, , ) = abi.decode( swapData[4:], (IERC20, IERC20, uint256, uint256, Transformation[]) ); // cache them for saving gas when casting from IERC20 -> address address _input = address(input); address _output = address(output); // Make sure both input and output tokens are NOT address(0) if (_input == address(0) || _output == address(0)) { revert ZeroAddress(); } // Make sure that the _input tokens target token is the _output if (rewardTokenToTargetToken[_input] != address(_output)) { revert WrongInputToken(); } uint256 inputBalBefore = input.balanceOf(address(this)); uint256 outputBalBefore = output.balanceOf(address(this)); // Make sure that we are swapping the entire reward token no partial swaps! if (inputBalBefore != inputTokenAmount) { revert PartialSwap(); } // Low level call to the ZERO_X_ROUTER constant address (bool success, ) = ZERO_X_ROUTER.call(swapData); require(success, "SWAP_FAILED"); uint256 outputBalAfter = output.balanceOf(address(this)); uint256 boughtOutput = outputBalAfter - outputBalBefore; // We MUST have something if (boughtOutput == 0) { revert ZeroOutputTokenAmount(); } // We MUST sell entire reward tokens for underlying no partial swaps! if (input.balanceOf(address(this)) != 0) { revert PartialSwap(); } } else { revert InvalidSwapData(); } } } ``` ### What are the drawbacks of using such a function in strategies? 1. **Aggregator trust is required** Aggregators operate as a black box, so we can't be sure if the quoted amount out is the most optimal one. It can be challenging to determine whether the amount out calculated via the aggregator is accurate. 1. **Off-chain interaction with aggregator API is necessary for swap data** To use this function, we need to create a bot to monitor rewards off-chain and send requests to the aggregator API to generate byte data, which is then passed to the function. We must validate the data inside the function to prevent any malicious actions from being taken. ### What are the benefits of using such a function in strategies? 1. **Governance only needs to select the target token to swap, with no need for routes inside the strategy** Governance only needs to choose the target token to swap, and the aggregator will handle the most efficient path. 2. **By using the aggregator's expertise, we can achieve higher yield by swapping tokens with the best efficiency** For example, suppose we want to sell 5000OP for sUSD using Uniswap and the 0x aggregator. In that case, by using the aggregator, we can achieve higher yield by swapping tokens with the best efficiency, as demonstrated in the following trade comparisons: UNISWAP TRADE ![](https://i.imgur.com/Kc6SPSy.png) 0x TRADE ![](https://i.imgur.com/iJydjLk.png) VELODROME TRADE ![](https://i.imgur.com/3rctyRy.png) **NOTE: We assume the UI displaying correct numbers here. In theory we assume aggregator will act as the best for us. 1inch can also be used for this example I went for 0x because it was easier.**