<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Rodrigo Herrera Itie</title>
        <link>https://paragraph.com/@rodrigo-herrera-itie</link>
        <description>undefined</description>
        <lastBuildDate>Sat, 25 Jul 2026 08:55:48 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <image>
            <title>Rodrigo Herrera Itie</title>
            <url>https://storage.googleapis.com/papyrus_images/3d6aaf0a3d87c9c94589fd23cb75c93f8deec0304ace0441c2e4396fc29efaf9.jpg</url>
            <link>https://paragraph.com/@rodrigo-herrera-itie</link>
        </image>
        <copyright>All rights reserved</copyright>
        <item>
            <title><![CDATA[Compound V2 in Depth]]></title>
            <link>https://paragraph.com/@rodrigo-herrera-itie/compound-v2-in-depth</link>
            <guid>QV7JUrSUrDkTsb24VHa1</guid>
            <pubDate>Thu, 19 Jan 2023 00:13:07 GMT</pubDate>
            <description><![CDATA[IntroductionCompound is a decentralized protocol which establishes money markets with algorithmically set interest rates based on supply and demand, allowing users to frictionlessly exchange the time value of Ethereum assets*. Compound V2 is one of the most important protocols on EVM chains. According to Defillama, it is the second most forked project by TVL. In this post, we are going to analyze the most important components of Compound, primarily money markets (cTokens) and its governance m...]]></description>
            <content:encoded><![CDATA[<h2 id="h-introduction" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Introduction</h2><p><em>Compound is a decentralized protocol which establishes money markets with algorithmically set interest rates based on supply and demand, allowing users to frictionlessly exchange the time value of Ethereum assets*.</em></p><p>Compound V2 is one of the most important protocols on EVM chains. According to Defillama, <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://defillama.com/forks">it is the second most forked project by TVL</a>.</p><p>In this post, we are going to analyze the most important components of Compound, primarily money markets (cTokens) and its governance model.</p><p><strong>NOTE:</strong> <em>You should have a good understanding of the basic Ethereum and Defi concepts to follow along. Primarily, you should be comfortable reading Solidity and the basics of Defi (erc20 tokens, incentives, etc.)</em></p><p><strong>This post is divided in 2 parts:</strong></p><p><strong><em>→ The first part goes through the general protocol architecture.</em></strong></p><p><strong><em>→ The second part goes through hands-on examples that demonstrate everything that was covered.</em></strong></p><p><strong>NOTE:</strong> You can find the github repo <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/rodrigoherrerai/Compound-V2-Examples">here</a>. We will go through the source in the second part, but it is advisable to read the code as we understand each topic.</p><h2 id="h-protocol-architecture" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Protocol Architecture</h2><h3 id="h-money-markets" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Money Markets</h3><p>The first concept to understand is money markets. You can think of a money market as a smart contract that holds 2 assets:</p><ol><li><p>The underlying asset (Eth, Dai, USDC, etc.).</p></li><li><p>The token representation of the underlying asset (cToken).</p></li></ol><p><strong>NOTE:</strong> <em>There are cases where money markets hold more than 2 assets, but compound V2 doesn’t.</em></p><p>In simple terms, you deposit an asset (think of Eth) into a smart contract and get a representation of it (cEth) in return. This token will increase or decrease in value depending on the rules of the smart contract.</p><p>The main conceptual difference between a money market and a traditional lending / borrowing platform is that the former is on-chain (trustless) while the latter requires you trusting an intermediate (platform).</p><p>One of the main benefits of this is that you can read the code and know that something will be executed with certainty and you also know the exact amount of liquidity that there is.</p><h3 id="h-ctokens" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">cTokens</h3><p>As previously mentioned, each market is implemented as a smart contract and the token representation in Compound is known as a “cToken”.</p><p>The cToken is basically an <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://ethereum.org/en/developers/docs/standards/tokens/erc-20/">ERC20 compatible token</a> with extra functionality.</p><p>You can mint cTokens by depositing the underlying token to the cToken smart contract.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/ba154898b18b600dd7538feda0578787ccf93e8549fa56cdb684542ada254306.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>*<em>You can find the full API of a cToken contract </em><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.compound.finance/v2/ctokens/"><em>here</em></a><em>.</em></p><p>Some important characteristics of a cToken:</p><ul><li><p>Each cToken will increase its value on a (almost) per block basis due to the interests earned.</p></li><li><p>You can use the cToken as collateral to borrow funds. You can also use it in other markets (it is an ERC20 compatible token).</p></li></ul><p><strong><em>Exchange Rate:</em></strong> The exchange rate between a cToken and the underlying asset e.g. (dai vs cDai or eth vs cEth) begins at 0.020 and increases at a rate equal to the compounding market interest rate.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/ef6fad1b3fdf1a415dadf24e62fc6762a3e7c8932f153bcb30493e488eea42e1.png" alt="exchange rate formula" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">exchange rate formula</figcaption></figure><p>Where:</p><p><strong>getCash():</strong> The amount of underlying balance owned by this cToken contract.</p><p><strong>totalBorrows():</strong> The amount of underlying currently loaned out by the market, and the amount upon which interest is accumulated to suppliers of the market.</p><p><strong>totalReserves():</strong> Reserves are an accounting entry in each cToken contract that represents a portion of historical interest set aside as cash which can be withdrawn or transferred through the protocol’s governance.</p><p><strong>totalSupply():</strong> The number of tokens currently in circulation in this cToken market.</p><p><strong>NOTE:</strong> <em>All cTokens have 8 decimals, while the underlying token can vary.</em></p><h3 id="h-interest-rates" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Interest Rates</h3><p>Interest rates are updated on every block as long as the ratio of borrowed assets to supplied assets has changed. The amount of the rates depend on the specific implementation of the smart contract.</p><p>There is an external smart contract that calculates the rates, both borrow and supply.</p><p>Let’s take the cEther money market as an example.</p><p>If we want to find out about the borrow rate per block, we would call the borrowRatePerBlock function from the specific cToken contract. The cToken contract (cEther for our example) will call another external contract that will get the rates.</p><pre data-type="codeBlock" text="function borrowRatePerBlock() override external view returns (uint) {
        return interestRateModel.getBorrowRate(getCashPrior(), totalBorrows, totalReserves);
    }
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">borrowRatePerBlock</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">override</span></span> <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint</span></span>) </span>{
        <span class="hljs-keyword">return</span> interestRateModel.getBorrowRate(getCashPrior(), totalBorrows, totalReserves);
    }
</code></pre><p>This is the reason why each cToken may have different interest rate implementations. For the cEther contract, the interest rate contract is called “LegacyJumpRateModelV2” you can find it <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://etherscan.io/address/0xF9583618169920c544Ec89795a346F487cB5a227#code">here</a>.</p><h3 id="h-liquidations" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Liquidations</h3><p><em>A user who has negative account liquidity is subject to liquidation by other users of the protocol to return his/her account liquidity back to positive (i.e. above the collateral requirement). When a liquidation occurs, a liquidator may repay some or all of an outstanding borrow on behalf of a borrower and in return receive a discounted amount of collateral held by the borrower; this discount is defined as the liquidation incentive.</em></p><p>A user’s <strong>account liquidity</strong> (you can read more about it <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.compound.finance/v2/comptroller/#account-liquidity">here</a>) is the amount in USD that a user is able to borrow.</p><p>We are going to go through an example to grasp this better, but before that we need to understand the collateral factor.</p><p>The <strong>collateral factor</strong> is a percentage that ranges between 0-90% that represents the amount that a user can borrow from minting cTokens. The more stable and liquid an asset is, the higher the collateral factor and vice versa.</p><p>Ok, so let’s suppose that Alice supplies 1 Eth to Compound. The price of Eth is $1,000 usd and the collateral factor is 90%. This means that Alice will have an account liquidity of $900 usd (90% of $1,000).</p><p>Then Alice borrows 900 usd of DAI, her new account liquidity would be 0. If the price of Eth goes down, her account liquidity would be negative and liquidators would be able to trigger this liquidation.</p><p><em>*To see this in code, go to the file located under test/AccounLiquidity.t.sol</em></p><p>One last characteristic to understand is that the system doesn’t guarantee that a liquidation will take place. It provides incentives so users (liquidators) are constantly watching for a liquidation opportunity so they call the liquidation function and get some money in return.</p><h3 id="h-comptroller" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Comptroller</h3><p><em>The Comptroller is the risk management layer of the Compound protocol; it determines how much collateral a user is required to maintain, and whether (and by how much) a user can be liquidated. Each time a user interacts with a cToken, the Comptroller is asked to approve or deny the transaction.</em></p><p>You can think of the Comptroller as a provider and verifier contract that gives information to the cTokens and checks that a user can execute a given transaction.</p><p>* You can find the Comptroller <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://etherscan.io/address/0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B#code">here</a>.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/eeeaae8b4cbee55d6871e0c3eedbfbc630be80b7ab793008c72db81838e4ca9a.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p><strong>NOTE:</strong> <em>This diagram is not 100% accurate as the architecture is implemented through upgradable proxies but the general concept remains intact.</em></p><h2 id="h-governance" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Governance</h2><p>One crucial aspect of smart contract systems is its governance model.</p><p>This is probably the most important governance smart contracts in EVM chains, it is used in many protocols.</p><p>This is a complex topic for a short post, so to simplify it, we can think of what governance is by asking the following questions:</p><ul><li><p>Are the contracts upgradable ? If they are, Who can upgrade them ?</p></li><li><p>Are there any other special privileges in the contracts ? For example, can an admin key blacklist certain users ?</p></li></ul><p>These are the most fundamental points that we should focus on smart contract systems. If there are privileged keys that can blacklist users, upgrade the contracts or pause them, then the system is not very trustless or secure.</p><p>Ok, let’s go back to Compound.</p><p>Compound V2 is built around a decentralized voting process. To understand this, it is important to know what upgradable proxies are, you can read about them <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.openzeppelin.com/upgrades-plugins/1.x/proxies">here</a>.</p><p>I am going to go through the most important aspects of the governance process, to simplify it as much as possible.</p><p>If we remember from the previous part, the Comptroller is the contract that basically does a bunch of safety checks for the cTokens.</p><p>The Comptroller is architected as an upgradable proxy, the name of the actual proxy is named the “Unitroller”, you can find the contract’s code <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://etherscan.io/address/0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B#code">here</a>.</p><p>The Unitroller delegates all calls to the Comptroller singleton, but it also has some additional methods, primarily changing the implementation contract and changing the admin.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/f05aaab5c018719e4592b5ca46c346319bd7d8cd05a84a05679a523e855b81be.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>So there are a couple of important security things going on here.</p><p>First of all, if someone upgrades the implementation contract (we will do it later on as a poc) to a malicious address, and changes the admin, then all user’s funds will be lost as there is no way to get it back.</p><p>The process basically works by proposing a proposal, voting (favour or against) and executing the proposal if succeeded.</p><p>Let’s get into the low level process of how making changes to the contract works.</p><h3 id="h-1-propose" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">1. Propose</h3><p>The first thing is to propose a proposal, and this is done on chain by calling the “propose” function from the GovernorBravoDelegate contract. You can find it <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://etherscan.io/address/0xc0Da02939E1441F497fd74F78cE7Decb17B66529#code">here</a>.</p><pre data-type="codeBlock" text="function propose(address[] memory targets, uint[] memory values, string[] memory signatures, bytes[] memory calldatas, string memory description) public returns (uint)
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">propose</span>(<span class="hljs-params"><span class="hljs-keyword">address</span>[] <span class="hljs-keyword">memory</span> targets, <span class="hljs-keyword">uint</span>[] <span class="hljs-keyword">memory</span> values, <span class="hljs-keyword">string</span>[] <span class="hljs-keyword">memory</span> signatures, <span class="hljs-keyword">bytes</span>[] <span class="hljs-keyword">memory</span> calldatas, <span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> description</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint</span></span>)
</span></code></pre><p>In order to be able to call the propose function, the proposer (msg.sender) should have at least x amount of COMP tokens or votes. This minimum amount of votes to propose a proposal is called the “proposalThreshold” and it is currently 25,000. This means that if you have less than 25,000 COMP tokens or votes, you are not able to be a proposer.</p><p>Once the proposal goes through, there is a voting delay of 13,140 blocks (around 2 days). This means that we need to wait 13,140 blocks to start the voting process .</p><h3 id="h-2-vote" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">2. Vote</h3><p>Once the voting delay period has passed, the process of voting beginns.</p><p>*Users that have “votes” are the ones that are able to vote. For example, if you have 100 COMP tokens and you delegate them to yourself, then you have 100 votes. You can also delegate your 100 COMP tokens to someone else, deferring your votes.</p><p>The process is very simple, there is a function called “castVote” that voters call with their vote preference. (There are more voting functions but they are all pretty much the same).</p><pre data-type="codeBlock" text="function castVote(uint proposalId, uint8 support) external 
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">castVote</span>(<span class="hljs-params"><span class="hljs-keyword">uint</span> proposalId, <span class="hljs-keyword">uint8</span> support</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> 
</span></code></pre><p>You just need to pass the id of the proposal and your vote support (0=against, 1=for, 2=abstain).</p><p>Once you vote, all the votes that your account owns will be summed up to the proposal’s state. For example, if you have 10,000 COMP tokens and you are the delegator of your own tokens, and vote in favour of a proposal, then that proposal will increase its “for” votes by 10,000.</p><p>The voting period currently lasts for 19,710 blocks (around 3 days).</p><p>Once the voting period ends, it is time to queue the proposal.</p><h3 id="h-3-queue" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">3. Queue</h3><p>Once all the votes were gathered and the voting period ends, the proposal either “Succeeded” or got “Defeated”. If the proposal succeeded, then the next step can go through.</p><p>The proposal is successful if:</p><p><strong>a)</strong> There were more votes in favour than against</p><p><strong>b)</strong> The “for” votes are greater than the quorumVotes.</p><p>There is a minimum amount of “for” votes in order for the proposal to go through. Currently the minimum or &quot;quorumVotes” is 400,000.</p><p>The next step (if the proposal succeeds) is to queue it to the Timelock contract. You can find the contract <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://etherscan.io/address/0x6d903f6003cca6255D85CcA4D3B5E5146dC33925">here</a>.</p><p>Once the transaction has been queued to the Timelock contract there is a 172,800 seconds delay (2 days). Once the delay is over, the transaction can be executed.</p><h3 id="h-4-execute" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">4. Execute</h3><p>After the 2 days Timelock delay has passed, the proposal can be executed.</p><p>This is simply done by calling the “execute” transaction from the GovernorBravoDelegate contract.</p><p>This function will execute the proposal from the context of the Timelock contract.</p><p>A more realistic architecture of how the governance contracts are implemented:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/f0a7dbb4c83c66c11b4e3a2532d900896fe31caf36a6ebad237a2c47f79817c8.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>And visual representation of the proposal process:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/696f00ceed756af33f9f1cc4e2f9aceea4ad4b2419093d3e643eaf41159e3bac.png" alt="https://docs.compound.finance/v2/governance/" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">https://docs.compound.finance/v2/governance/</figcaption></figure><p>If a protocol has a bad structured governance model (easy to manipulate), then the governance model becomes the security’s weakest link. As you can see from the previous explanation, the governance of a protocol like Compound has the power to make critical changes that affect (in a positive or negative way) all users.</p><p>In Compound’s case, the security of the protocol goes hand by hand with the COMP token valuation. If the total market cap is very low, the possibilities of someone taking control rise significantly.</p><h2 id="h-hands-on" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Hands-On</h2><p>For this next part, we are going to execute the most common operations in Compound.</p><p>You can find the github repo <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/rodrigoherrerai/Compound-V2-Examples">here</a> (you should clone the repo to follow along).</p><p><strong>NOTE:</strong> <em>The examples are done by forking Ethereum mainnet at block </em><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://etherscan.io/block/16401180"><em>16401180</em></a><em> and using Foundry.</em></p><h3 id="h-governance-upgrading-the-comptroller" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Governance (upgrading the Comptroller)</h3><p>This is probably the most interesting example. We are going to go through the entire proposal lifecycle that we covered in the Governance section.</p><p>*The file is located at test/UpgradeComptroller.t.sol</p><p>In this example, we are going to create a proposal, vote, win it in our favour, and then execute it. The proposal is to change the Comptroller implementation to a malicious singleton.</p><p>Here is the code:</p><pre data-type="codeBlock" text="// SPDX-License-Identifier: MIT
pragma solidity &gt;= 0.8.0;

import &quot;forge-std/Test.sol&quot;;

import &quot;../src/NewComptroller.sol&quot;;
import &quot;./utils/TestUtils.sol&quot;;

/// @notice Example contract that upgrades the Comptroller.
/// This example also goes through the entire governance journey:
/// 1. Create the proposal
/// 2. Vote
/// 3. Queue it to the Timelock
/// 4. Execute the proposal
contract UpgradeComptrollerTest is Test, TestUtils {
    NewComptroller newComptroller;

    receive() external payable {}

    function setUp() public {
        // Fork mainnet at block 16_401_180.
        cheat.createSelectFork(&quot;mainnet&quot;, BLOCK_NUMBER);

        ///// We fund this test contract with 400K COMP tokens.
        uint256 TRANSFER_AMOUNT = 400_000 * 1e18;

        cheat.startPrank(0x2775b1c75658Be0F640272CCb8c72ac986009e38);
        comp.transfer(address(this), TRANSFER_AMOUNT);
        cheat.stopPrank();

        assertEq(comp.balanceOf(address(this)), TRANSFER_AMOUNT);
        /////

        ///// Now we delegate all votes to this address.
        comp.delegate(address(this));
        /////

        // We increase the block number by 1 so the votes are actionable.
        vm.roll(block.number + 1);
        assertEq(comp.getPriorVotes(address(this), block.number - 1), TRANSFER_AMOUNT);

        // We create the new singleton Comptroller.
        newComptroller = new NewComptroller();
    }

    /// @notice This test function goes through the complete governance model of Compound.
    /// It ends up upgrading the comptroller singleton.
    function testUpgradeComptroller() public {
        // 1. We CREATE and send the PROPOSAL.
        uint256 proposalId = propose();

        // We increase the block number by the voting delay.
        uint256 votingDelay = gBravo.votingDelay(); // 13140 blocks
        vm.roll(block.number + votingDelay + 1);

        // 2. We VOTE in favor of our proposal.
        gBravo.castVote(proposalId, 1); // 0=against, 1=for, 2=abstain

        // We increase the block number so it gets to the voting end.
        uint256 votingPeriod = gBravo.votingPeriod(); // 19710 blocks
        vm.roll(block.number + votingPeriod);

        // 3. We QUEUE the transaction to the timelock.
        gBravo.queue(proposalId);

        // We increase the timestamp number by the timelock delay.
        uint256 timelockDelay = timelock.delay();

        vm.warp(block.timestamp + timelockDelay);

        // 4. We EXECUTE the PROPOSAL.
        gBravo.execute(proposalId);

        // We accept the implementation.
        newComptroller.acceptImplementation(address(comptroller));

        // We check that the comptroller has been upgraded.
        (bool success, bytes memory data) =
            address(comptroller).staticcall(abi.encodeWithSignature(&quot;testImplementation()&quot;));
        require(success);
        string memory result = abi.decode(data, (string));
        assertEq(result, &quot;I am the new Comptroller&quot;);
    }

    function propose() internal returns (uint256) {
        address[] memory targets = new addressUnsupported embed;
        targets[0] = address(comptroller);

        uint256[] memory values = new uintUnsupported embed;
        values[0] = 0;

        string[] memory signatures = new stringUnsupported embed;
        signatures[0] = &quot;&quot;;

        bytes[] memory calldatas = new bytesUnsupported embed;
        calldatas[0] = abi.encodeWithSignature(&quot;_setPendingImplementation(address)&quot;, address(newComptroller));

        string memory description = &quot;Upgrades the Comptroller&quot;;

        uint256 proposalId = gBravo.propose(targets, values, signatures, calldatas, description);
        require(proposalId &gt; 0, &quot;Proposal failed&quot;);
        return proposalId;
    }
}
 
"><code><span class="hljs-comment">// SPDX-License-Identifier: MIT</span>
<span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> >= 0.8.0;</span>

<span class="hljs-keyword">import</span> <span class="hljs-string">"forge-std/Test.sol"</span>;

<span class="hljs-keyword">import</span> <span class="hljs-string">"../src/NewComptroller.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"./utils/TestUtils.sol"</span>;

<span class="hljs-comment">/// @notice Example contract that upgrades the Comptroller.</span>
<span class="hljs-comment">/// This example also goes through the entire governance journey:</span>
<span class="hljs-comment">/// 1. Create the proposal</span>
<span class="hljs-comment">/// 2. Vote</span>
<span class="hljs-comment">/// 3. Queue it to the Timelock</span>
<span class="hljs-comment">/// 4. Execute the proposal</span>
<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">UpgradeComptrollerTest</span> <span class="hljs-keyword">is</span> <span class="hljs-title">Test</span>, <span class="hljs-title">TestUtils</span> </span>{
    NewComptroller newComptroller;

    <span class="hljs-function"><span class="hljs-keyword">receive</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">payable</span></span> </span>{}

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">setUp</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> </span>{
        <span class="hljs-comment">// Fork mainnet at block 16_401_180.</span>
        cheat.createSelectFork(<span class="hljs-string">"mainnet"</span>, BLOCK_NUMBER);

        <span class="hljs-comment">///// We fund this test contract with 400K COMP tokens.</span>
        <span class="hljs-keyword">uint256</span> TRANSFER_AMOUNT <span class="hljs-operator">=</span> <span class="hljs-number">400_000</span> <span class="hljs-operator">*</span> <span class="hljs-number">1e18</span>;

        cheat.startPrank(<span class="hljs-number">0x2775b1c75658Be0F640272CCb8c72ac986009e38</span>);
        comp.<span class="hljs-built_in">transfer</span>(<span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>), TRANSFER_AMOUNT);
        cheat.stopPrank();

        assertEq(comp.balanceOf(<span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>)), TRANSFER_AMOUNT);
        <span class="hljs-comment">/////</span>

        <span class="hljs-comment">///// Now we delegate all votes to this address.</span>
        comp.delegate(<span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>));
        <span class="hljs-comment">/////</span>

        <span class="hljs-comment">// We increase the block number by 1 so the votes are actionable.</span>
        vm.roll(<span class="hljs-built_in">block</span>.<span class="hljs-built_in">number</span> <span class="hljs-operator">+</span> <span class="hljs-number">1</span>);
        assertEq(comp.getPriorVotes(<span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>), <span class="hljs-built_in">block</span>.<span class="hljs-built_in">number</span> <span class="hljs-operator">-</span> <span class="hljs-number">1</span>), TRANSFER_AMOUNT);

        <span class="hljs-comment">// We create the new singleton Comptroller.</span>
        newComptroller <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> NewComptroller();
    }

    <span class="hljs-comment">/// @notice This test function goes through the complete governance model of Compound.</span>
    <span class="hljs-comment">/// It ends up upgrading the comptroller singleton.</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">testUpgradeComptroller</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> </span>{
        <span class="hljs-comment">// 1. We CREATE and send the PROPOSAL.</span>
        <span class="hljs-keyword">uint256</span> proposalId <span class="hljs-operator">=</span> propose();

        <span class="hljs-comment">// We increase the block number by the voting delay.</span>
        <span class="hljs-keyword">uint256</span> votingDelay <span class="hljs-operator">=</span> gBravo.votingDelay(); <span class="hljs-comment">// 13140 blocks</span>
        vm.roll(<span class="hljs-built_in">block</span>.<span class="hljs-built_in">number</span> <span class="hljs-operator">+</span> votingDelay <span class="hljs-operator">+</span> <span class="hljs-number">1</span>);

        <span class="hljs-comment">// 2. We VOTE in favor of our proposal.</span>
        gBravo.castVote(proposalId, <span class="hljs-number">1</span>); <span class="hljs-comment">// 0=against, 1=for, 2=abstain</span>

        <span class="hljs-comment">// We increase the block number so it gets to the voting end.</span>
        <span class="hljs-keyword">uint256</span> votingPeriod <span class="hljs-operator">=</span> gBravo.votingPeriod(); <span class="hljs-comment">// 19710 blocks</span>
        vm.roll(<span class="hljs-built_in">block</span>.<span class="hljs-built_in">number</span> <span class="hljs-operator">+</span> votingPeriod);

        <span class="hljs-comment">// 3. We QUEUE the transaction to the timelock.</span>
        gBravo.queue(proposalId);

        <span class="hljs-comment">// We increase the timestamp number by the timelock delay.</span>
        <span class="hljs-keyword">uint256</span> timelockDelay <span class="hljs-operator">=</span> timelock.delay();

        vm.warp(<span class="hljs-built_in">block</span>.<span class="hljs-built_in">timestamp</span> <span class="hljs-operator">+</span> timelockDelay);

        <span class="hljs-comment">// 4. We EXECUTE the PROPOSAL.</span>
        gBravo.execute(proposalId);

        <span class="hljs-comment">// We accept the implementation.</span>
        newComptroller.acceptImplementation(<span class="hljs-keyword">address</span>(comptroller));

        <span class="hljs-comment">// We check that the comptroller has been upgraded.</span>
        (<span class="hljs-keyword">bool</span> success, <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">memory</span> data) <span class="hljs-operator">=</span>
            <span class="hljs-keyword">address</span>(comptroller).<span class="hljs-built_in">staticcall</span>(<span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encodeWithSignature</span>(<span class="hljs-string">"testImplementation()"</span>));
        <span class="hljs-built_in">require</span>(success);
        <span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> result <span class="hljs-operator">=</span> <span class="hljs-built_in">abi</span>.<span class="hljs-built_in">decode</span>(data, (<span class="hljs-keyword">string</span>));
        assertEq(result, <span class="hljs-string">"I am the new Comptroller"</span>);
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">propose</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">internal</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span></span>) </span>{
        <span class="hljs-keyword">address</span>[] <span class="hljs-keyword">memory</span> targets <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> addressUnsupported embed;
        targets[<span class="hljs-number">0</span>] <span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(comptroller);

        <span class="hljs-keyword">uint256</span>[] <span class="hljs-keyword">memory</span> values <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> uintUnsupported embed;
        values[<span class="hljs-number">0</span>] <span class="hljs-operator">=</span> <span class="hljs-number">0</span>;

        <span class="hljs-keyword">string</span>[] <span class="hljs-keyword">memory</span> signatures <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> stringUnsupported embed;
        signatures[<span class="hljs-number">0</span>] <span class="hljs-operator">=</span> <span class="hljs-string">""</span>;

        <span class="hljs-keyword">bytes</span>[] <span class="hljs-keyword">memory</span> calldatas <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> bytesUnsupported embed;
        calldatas[<span class="hljs-number">0</span>] <span class="hljs-operator">=</span> <span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encodeWithSignature</span>(<span class="hljs-string">"_setPendingImplementation(address)"</span>, <span class="hljs-keyword">address</span>(newComptroller));

        <span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> description <span class="hljs-operator">=</span> <span class="hljs-string">"Upgrades the Comptroller"</span>;

        <span class="hljs-keyword">uint256</span> proposalId <span class="hljs-operator">=</span> gBravo.propose(targets, values, signatures, calldatas, description);
        <span class="hljs-built_in">require</span>(proposalId <span class="hljs-operator">></span> <span class="hljs-number">0</span>, <span class="hljs-string">"Proposal failed"</span>);
        <span class="hljs-keyword">return</span> proposalId;
    }
}
 
</code></pre><h3 id="h-supply-and-redeem" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Supply &amp; Redeem</h3><p>In this example we are going to supply Eth to the CEth market and then redeem it.</p><p>*The file is located at test/SupplyAndRedeem.t.sol</p><p>The steps to supply to a Compound market and get a cToken in return are the following:</p><ol><li><p>Deposit the underlying asset by calling the “mint()” function.</p></li></ol><p>The mint function from the cToken will:</p><ul><li><p>Transfer the underlying token amount (mintAmount) to the cToken contract or msg.value for cEther.</p></li><li><p>Mint the corresponding cTokens (depositAmount / exchangeRate).</p></li></ul><p>We will be mainly interacting with the cEther contract, you can find it <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://etherscan.io/address/0x4Ddc2D193948926D02f9B1fE9e1daa0718270ED5">here</a>.</p><p>Here is the complete test case for:</p><p>a) calculating the exchange rate</p><p>b) supplying</p><p>c) redeeming</p><pre data-type="codeBlock" text="// SPDX-License-Identifier: MIT
pragma solidity &gt;= 0.8.0;

import &quot;forge-std/Test.sol&quot;;

import &quot;./utils/TestUtils.sol&quot;;

/// @notice Example contract that supplies an asset to Compound and redeems it.
contract SupplyAndRedeemTest is Test, TestUtils {
    receive() external payable {}

    function setUp() public {
        // Fork mainnet at block 16401180.
        cheat.createSelectFork(&quot;mainnet&quot;, BLOCK_NUMBER);
    }

    /// @dev Calculates the exchange rate between eth and cEther.
    function getExchangeRate() internal returns (uint256) {
        // exchangeRate = (totalCash + totalBorrows - totalReserves) / totalSupply.
        uint256 totalCash = cEther.getCash();
        assertEq(totalCash, address(cEther).balance);

        uint256 totalBorrows = cEther.totalBorrowsCurrent();
        assert(totalBorrows &gt; 0);

        uint256 totalReserves = cEther.totalReserves();
        assert(totalReserves &gt; 0);

        uint256 totalSupply = cEther.totalSupply();
        assert(totalSupply &gt; 0);

        uint256 exchangeRate = 1e18 * (totalCash + totalBorrows - totalReserves) / totalSupply;
        return exchangeRate;
    }

    /// @notice Supplies Eth to Compound, checks balances, accrues interests, and redeems.
    function testSupplyAndRedeem() public {
        // We save the initial eth balance to compare it later on.
        uint256 initialEthBalance = address(this).balance;

        // Our initial cEther balance should be 0.
        assertEq(cEther.balanceOf(address(this)), 0);

        // We supply 1 ether.
        cEther.mint{value: 1 ether}();

        // We could get the exchange rate by calling &quot;exchangeRateCurrent()&quot; directly.
        // We calculate it ourselves for learning purposes.
        uint256 exchangeRate = getExchangeRate();

        // We get the balance of cEther after supplying.
        uint256 cEtherBalance = cEther.balanceOf(address(this));

        // We get the amount of cEther that we should have.
        uint256 mintTokens = 1 ether * 1e18 / exchangeRate;
        assertEq(cEtherBalance, mintTokens);

        // We incrase the block number by 1.
        vm.roll(block.number + 1);

        // We redeem all the cEther.
        require(cEther.redeem(cEther.balanceOf(address(this))) == 0, &quot;redeem failed&quot;);

        // We should have 0 cEther.
        assertEq(cEther.balanceOf(address(this)), 0);

        // Eth + interests. We should have more eth with 1 block of interests.
        assert(address(this).balance &gt; initialEthBalance);
    }
}
 
"><code><span class="hljs-comment">// SPDX-License-Identifier: MIT</span>
<span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> >= 0.8.0;</span>

<span class="hljs-keyword">import</span> <span class="hljs-string">"forge-std/Test.sol"</span>;

<span class="hljs-keyword">import</span> <span class="hljs-string">"./utils/TestUtils.sol"</span>;

<span class="hljs-comment">/// @notice Example contract that supplies an asset to Compound and redeems it.</span>
<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">SupplyAndRedeemTest</span> <span class="hljs-keyword">is</span> <span class="hljs-title">Test</span>, <span class="hljs-title">TestUtils</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">receive</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">payable</span></span> </span>{}

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">setUp</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> </span>{
        <span class="hljs-comment">// Fork mainnet at block 16401180.</span>
        cheat.createSelectFork(<span class="hljs-string">"mainnet"</span>, BLOCK_NUMBER);
    }

    <span class="hljs-comment">/// @dev Calculates the exchange rate between eth and cEther.</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getExchangeRate</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">internal</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span></span>) </span>{
        <span class="hljs-comment">// exchangeRate = (totalCash + totalBorrows - totalReserves) / totalSupply.</span>
        <span class="hljs-keyword">uint256</span> totalCash <span class="hljs-operator">=</span> cEther.getCash();
        assertEq(totalCash, <span class="hljs-keyword">address</span>(cEther).<span class="hljs-built_in">balance</span>);

        <span class="hljs-keyword">uint256</span> totalBorrows <span class="hljs-operator">=</span> cEther.totalBorrowsCurrent();
        <span class="hljs-built_in">assert</span>(totalBorrows <span class="hljs-operator">></span> <span class="hljs-number">0</span>);

        <span class="hljs-keyword">uint256</span> totalReserves <span class="hljs-operator">=</span> cEther.totalReserves();
        <span class="hljs-built_in">assert</span>(totalReserves <span class="hljs-operator">></span> <span class="hljs-number">0</span>);

        <span class="hljs-keyword">uint256</span> totalSupply <span class="hljs-operator">=</span> cEther.totalSupply();
        <span class="hljs-built_in">assert</span>(totalSupply <span class="hljs-operator">></span> <span class="hljs-number">0</span>);

        <span class="hljs-keyword">uint256</span> exchangeRate <span class="hljs-operator">=</span> <span class="hljs-number">1e18</span> <span class="hljs-operator">*</span> (totalCash <span class="hljs-operator">+</span> totalBorrows <span class="hljs-operator">-</span> totalReserves) <span class="hljs-operator">/</span> totalSupply;
        <span class="hljs-keyword">return</span> exchangeRate;
    }

    <span class="hljs-comment">/// @notice Supplies Eth to Compound, checks balances, accrues interests, and redeems.</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">testSupplyAndRedeem</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> </span>{
        <span class="hljs-comment">// We save the initial eth balance to compare it later on.</span>
        <span class="hljs-keyword">uint256</span> initialEthBalance <span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>).<span class="hljs-built_in">balance</span>;

        <span class="hljs-comment">// Our initial cEther balance should be 0.</span>
        assertEq(cEther.balanceOf(<span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>)), <span class="hljs-number">0</span>);

        <span class="hljs-comment">// We supply 1 ether.</span>
        cEther.mint{<span class="hljs-built_in">value</span>: <span class="hljs-number">1</span> <span class="hljs-literal">ether</span>}();

        <span class="hljs-comment">// We could get the exchange rate by calling "exchangeRateCurrent()" directly.</span>
        <span class="hljs-comment">// We calculate it ourselves for learning purposes.</span>
        <span class="hljs-keyword">uint256</span> exchangeRate <span class="hljs-operator">=</span> getExchangeRate();

        <span class="hljs-comment">// We get the balance of cEther after supplying.</span>
        <span class="hljs-keyword">uint256</span> cEtherBalance <span class="hljs-operator">=</span> cEther.balanceOf(<span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>));

        <span class="hljs-comment">// We get the amount of cEther that we should have.</span>
        <span class="hljs-keyword">uint256</span> mintTokens <span class="hljs-operator">=</span> <span class="hljs-number">1</span> <span class="hljs-literal">ether</span> <span class="hljs-operator">*</span> <span class="hljs-number">1e18</span> <span class="hljs-operator">/</span> exchangeRate;
        assertEq(cEtherBalance, mintTokens);

        <span class="hljs-comment">// We incrase the block number by 1.</span>
        vm.roll(<span class="hljs-built_in">block</span>.<span class="hljs-built_in">number</span> <span class="hljs-operator">+</span> <span class="hljs-number">1</span>);

        <span class="hljs-comment">// We redeem all the cEther.</span>
        <span class="hljs-built_in">require</span>(cEther.redeem(cEther.balanceOf(<span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>))) <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>, <span class="hljs-string">"redeem failed"</span>);

        <span class="hljs-comment">// We should have 0 cEther.</span>
        assertEq(cEther.balanceOf(<span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>)), <span class="hljs-number">0</span>);

        <span class="hljs-comment">// Eth + interests. We should have more eth with 1 block of interests.</span>
        <span class="hljs-built_in">assert</span>(<span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>).<span class="hljs-built_in">balance</span> <span class="hljs-operator">></span> initialEthBalance);
    }
}
 
</code></pre><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"></h3><p>Borrow &amp; Repay</p><p>In this example, we supply Eth as collateral, enter the markets, borrow Dai and then repay it.</p><p>*The file is located at test/BorrowAndRepay.t.sol</p><pre data-type="codeBlock" text="// SPDX-License-Identifier: MIT
pragma solidity &gt;= 0.8.0;

import &quot;forge-std/Test.sol&quot;;

import &quot;./utils/TestUtils.sol&quot;;

/// @notice Example contract that borrows and repays from Compound.
contract BorrowAndRepayTest is Test, TestUtils {
    function setUp() public {
        // Fork mainnet at block 16401180.
        cheat.createSelectFork(&quot;mainnet&quot;, BLOCK_NUMBER);
    }

    /// @notice Supplies Eth to Compound, checks balances, accrues interest, and redeems.
    function testBorrowAndRepay() public {
        // We shouldn&apos;t have any Dai balance at this time.
        assertEq(dai.balanceOf(address(this)), 0);

        ///// We need to supply some eth for collateral.
        cEther.mint{value: 1 ether}();

        // We enter the markets.
        address[] memory cTokens = new addressUnsupported embed;
        cTokens[0] = address(cEther);
        comptroller.enterMarkets(cTokens); // &lt;- we enter here

        // Checks
        address[] memory assetsIn = comptroller.getAssetsIn(address(this));
        assertEq(assetsIn[0], address(cEther));

        /////

        ///// Now we borrow some dai.
        uint256 borrowAmount = 500 * 1e18; // 500 dai
        cDai.borrow(borrowAmount);

        // Checks .. We should have 500 dai
        assertEq(dai.balanceOf(address(this)), borrowAmount);

        /////

        ///// We repay the dai.
        dai.approve(address(cDai), borrowAmount);
        cDai.repayBorrow(borrowAmount);

        assertEq(dai.balanceOf(address(this)), 0);
    }
}
"><code><span class="hljs-comment">// SPDX-License-Identifier: MIT</span>
<span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> >= 0.8.0;</span>

<span class="hljs-keyword">import</span> <span class="hljs-string">"forge-std/Test.sol"</span>;

<span class="hljs-keyword">import</span> <span class="hljs-string">"./utils/TestUtils.sol"</span>;

<span class="hljs-comment">/// @notice Example contract that borrows and repays from Compound.</span>
<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">BorrowAndRepayTest</span> <span class="hljs-keyword">is</span> <span class="hljs-title">Test</span>, <span class="hljs-title">TestUtils</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">setUp</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> </span>{
        <span class="hljs-comment">// Fork mainnet at block 16401180.</span>
        cheat.createSelectFork(<span class="hljs-string">"mainnet"</span>, BLOCK_NUMBER);
    }

    <span class="hljs-comment">/// @notice Supplies Eth to Compound, checks balances, accrues interest, and redeems.</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">testBorrowAndRepay</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> </span>{
        <span class="hljs-comment">// We shouldn't have any Dai balance at this time.</span>
        assertEq(dai.balanceOf(<span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>)), <span class="hljs-number">0</span>);

        <span class="hljs-comment">///// We need to supply some eth for collateral.</span>
        cEther.mint{<span class="hljs-built_in">value</span>: <span class="hljs-number">1</span> <span class="hljs-literal">ether</span>}();

        <span class="hljs-comment">// We enter the markets.</span>
        <span class="hljs-keyword">address</span>[] <span class="hljs-keyword">memory</span> cTokens <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> addressUnsupported embed;
        cTokens[<span class="hljs-number">0</span>] <span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(cEther);
        comptroller.enterMarkets(cTokens); <span class="hljs-comment">// &#x3C;- we enter here</span>

        <span class="hljs-comment">// Checks</span>
        <span class="hljs-keyword">address</span>[] <span class="hljs-keyword">memory</span> assetsIn <span class="hljs-operator">=</span> comptroller.getAssetsIn(<span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>));
        assertEq(assetsIn[<span class="hljs-number">0</span>], <span class="hljs-keyword">address</span>(cEther));

        <span class="hljs-comment">/////</span>

        <span class="hljs-comment">///// Now we borrow some dai.</span>
        <span class="hljs-keyword">uint256</span> borrowAmount <span class="hljs-operator">=</span> <span class="hljs-number">500</span> <span class="hljs-operator">*</span> <span class="hljs-number">1e18</span>; <span class="hljs-comment">// 500 dai</span>
        cDai.borrow(borrowAmount);

        <span class="hljs-comment">// Checks .. We should have 500 dai</span>
        assertEq(dai.balanceOf(<span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>)), borrowAmount);

        <span class="hljs-comment">/////</span>

        <span class="hljs-comment">///// We repay the dai.</span>
        dai.approve(<span class="hljs-keyword">address</span>(cDai), borrowAmount);
        cDai.repayBorrow(borrowAmount);

        assertEq(dai.balanceOf(<span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>)), <span class="hljs-number">0</span>);
    }
}
</code></pre><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"></h3><p>References:</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://compound.finance/documents/Compound.Whitepaper.pdf">https://compound.finance/documents/Compound.Whitepaper.pdf</a></p><div data-type="youtube" videoId="aTp9er6S73M">
      <div class="youtube-player" data-id="aTp9er6S73M" style="background-image: url('https://i.ytimg.com/vi/aTp9er6S73M/hqdefault.jpg'); background-size: cover; background-position: center">
        <a href="https://www.youtube.com/watch?v=aTp9er6S73M">
          <img src="{{DOMAIN}}/editor/youtube/play.png" class="play"/>
        </a>
      </div></div><div data-type="youtube" videoId="d9_ZEvLDSl8">
      <div class="youtube-player" data-id="d9_ZEvLDSl8" style="background-image: url('https://i.ytimg.com/vi/d9_ZEvLDSl8/hqdefault.jpg'); background-size: cover; background-position: center">
        <a href="https://www.youtube.com/watch?v=d9_ZEvLDSl8">
          <img src="{{DOMAIN}}/editor/youtube/play.png" class="play"/>
        </a>
      </div></div><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.compound.finance/v2/">https://docs.compound.finance/v2/</a></p>]]></content:encoded>
            <author>rodrigo-herrera-itie@newsletter.paragraph.com (Rodrigo Herrera Itie)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/9ce4121b63aa91ddcb2c1bd07523522c19eb5f9d950463ffc6abb348037de2d9.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Uniswap V2 in Depth
]]></title>
            <link>https://paragraph.com/@rodrigo-herrera-itie/uniswap-v2-in-depth</link>
            <guid>3MRG2Jk9OYlX0MTlFsnT</guid>
            <pubDate>Sun, 28 Aug 2022 17:26:30 GMT</pubDate>
            <description><![CDATA[A deep dive inside Uniswap V2 Welcome to the in Depth DeFi series! This series will be composed of several posts, each one will consist of the explanation of an individual protocol. The main purpose of this series is to understand from a first-principles approach how the top DeFi protocols work under the hood. We will review them from a theoretical and practical level. This means that we will understand the theory behind the protocol, but we are also going to dive deep into the actual impleme...]]></description>
            <content:encoded><![CDATA[<p>A deep dive inside Uniswap V2</p><p>Welcome to the in Depth DeFi series!</p><p>This series will be composed of several posts, each one will consist of the explanation of an individual protocol. The main purpose of this series is to understand from a first-principles approach how the top DeFi protocols work under the hood.</p><p>We will review them from a theoretical and practical level. This means that we will understand the theory behind the protocol, but we are also going to dive deep into the actual implementation (code).</p><p><strong><em>Pre-Requisites:</em></strong></p><p>In order to follow along, it is recommendable to have the following knowledge:</p><ol><li><p>General understanding of the EVM</p></li><li><p>Comfortable with Solidity</p></li><li><p>Basic math (algebra)</p></li><li><p>General understanding of the fundamentals of blockchains and smart contract protocols built on top</p></li></ol><h3 id="h-uniswap-v2" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">UniSwap V2</h3><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/8052187a1e09d033202ffbddd8cda16ec386c873959bfcc6c524a32f237cf30e.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p><strong><em>The post is divided into two main parts. The first one “System’s Components Overview” explains the theory and practice behind the most important components that make the system work. The second part “System’s CORE Implementation” goes through the actual architectural implementation.</em></strong></p><p>I believe that to truly understand a system in depth, you should be able to re-write it from scratch. Hopefully by the end of this post, you will be able to do that.</p><p>In order to start learning the system in a structured manner, it is important to ask ourselves some questions that are fundamental to the system:</p><ul><li><p>How does Uniswap’s pricing mechanism works ?</p></li><li><p>Who provides liquidity to the system ?</p></li><li><p>Why would someone provide liquidity to the system ?</p></li><li><p>How does the system represents the liquidity ? etc..</p></li></ul><p>Along the way, all the fundamental questions should be answered by understanding the protocol deeply.</p><p>Before starting, let’s quickly go to what Uniswap is:</p><p>Uniswap is a decentralized automated market maker protocol that allows anyone to swap token A for token B. The way automated market makers work is different from a traditional <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://en.wikipedia.org/wiki/Order_book">order book model</a>. The core principle that differentiates an automated market maker (Uniswap) from a traditional centralized order book, is that the former runs permissionless code on the blockchain allowing anyone to participate.</p><p>Before moving on, there are a couple of things I want to say.</p><ol><li><p><strong>Uniswap V2 is decentralized:</strong> Decentralization is not binary, but from my point of view (and many others) a decentralized smart contract protocol is one in which no central party can access the user’s funds. In other words, no one has a special key that can modify the contracts.</p></li><li><p><strong>There are 3 versions of Uniswap:</strong> For this post we will focus on V2 (we will do a V3 post in the future). But if you want to learn how Uniswap was created, I suggest watching <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.youtube.com/watch?v=LpjMgS4OVzs">this </a>video.</p></li><li><p><strong>A lot of AMM’s are a fork of Uniswap V2</strong>: Uniswap V2 is much simpler than V3, therefore, easier to maintain. So once you know how Uniswap V2 works, by default, you will know most of the AMM’s as well !</p></li></ol><p><strong><em>Let’s start !</em></strong></p><h1 id="h-systems-components-overview" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">System’s Components Overview</h1><p>To start getting a feeling of how the protocol works, we need to ask ourselves what is really happening when you are doing a simple swap. Of course, the purpose of the entire post is to explain this.</p><p>But at a basic level, you are interacting with a smart contract (pool) that “holds” reserves of two different tokens. For example, if you are swapping some WETH for USDC, you are interacting with the WETH/USDC smart contract pair (we will go into detail as to how this works later on). If you are interested, <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://etherscan.io/address/0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc">here</a> is the WETH/USDC smart contract pair on Etherscan.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/808172434bdc284b92ff7778d864951c4e376f684159d5a8708f3dd4c97ea9e3.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>So if you are buying USDC with WETH, you are increasing the supply of WETH in the pool and reducing the supply of USDC, therefore, increasing the relative price of USDC vs WETH.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/c0bd8b0ad1e96e308d7ebce52cf01253fe77fb4d32fbecec6a38315fc15867a6.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p><em>In the following sections, we will understand how the pool works (math behind it).</em></p><p>One important thing to keep in mind, is that UniswapV1’s pools were always traded against Eth. In other words, every pool was ETH/<strong>x</strong>Token.</p><p>In Uniswap V2, every pool is ERC-20/ERC-20. This later approach provides more flexibility to liquidity providers as they don’t need to rely 100% on ETH. So every time you are swapping ETH for another token, in reality, your ETH gets converted to WETH first.</p><h1 id="h-protocol-participants" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Protocol Participants</h1><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/a08ca2b35dd9cd18c96e48b606a404fbd7c53b7431c9a779e535687662fdfdce.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>Uniswap works by providing incentives to different participants to work with the system in order for them to profit. The main participants of the system are:</p><ol><li><p><strong>Traders:</strong> Traders can perform several actions in the system, some of them are:</p><ul><li><p>Speculating on the price of an asset.</p></li><li><p>Arbitrage software. For example, if the price of ETH in Uniswap is trading at $3,000 and in Coinbase is trading at $3,800, the arbitrageur would immediately buy ETH in Uniswap and resell-it in Coinbase for a profit.</p></li><li><p>Basic swap functionality (you want to change token x to token y) to use them in a Dapp. 2.</p></li></ul></li><li><p><strong>Liquidity Providers (LPs):</strong> LPs provide liquidity to the token pools. In rewards, they get the transaction fees.</p></li><li><p><strong>Developers:</strong> Developers create the systems and applications that exist in the ecosystem. In Uniswap terms, they can be core protocol developers, third-party Dapp developers that integrate with Uniswap, wallet developers etc…</p></li></ol><p>Before diving into the smart contracts, let’s understand the core concepts.</p><p>There are 3 basic components that we really need to understand in order to have a deep knowledge of Uniswap (and most of the DeFi protocols), they are the <strong>constant product formula, arbitrage and impermanent loss.</strong></p><h2 id="h-fees" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Fees</h2><p>UniswapV2 charges a flat 0.3% fee per trade, we will see how this is calculated in future sections. The fee goes to the liquidity providers, this is to reward people for their liquidity. The protocol can also trigger a change that would give 0.05% to the Uniswap team, that part of the fee would be discounted from the LP’s instead of the traders.</p><h1 id="h-constant-product-formula-in-depth" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Constant Product Formula in Depth</h1><p>A lot of the automated market makers work thanks to the constant product formula. It is a very simple yet powerful algorithm.</p><p>The <strong>constant product formula</strong> is the automated market algorithm that powers the Uniswap protocol (and a lot more AMM’s).</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/1e80b826e94cec843bdc2f5923be7a47ad0779dc82f2c4248a6a9d1ba9e2a1f9.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>This formula simply states that the invariant <strong>k</strong> must remain unchanged regarding of the outflow / inflow of <strong>x</strong> and <strong>y</strong>.</p><p>In other words, you can change the value of <strong>x</strong> and <strong>y</strong> to whatever you want, as long as <strong>k</strong> remains the same.</p><p><strong>For example:</strong></p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/bec4b0a57e88d0ad7a5883813ac28613b9b5dfbfd04ac94338ee95c87b80b6a4.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>What are <strong>x</strong> and <strong>y</strong> ? <strong>x</strong> and <strong>y</strong> are the reserves of the tokens in the pool. For example, if you are swapping DAI for WETH, you are interacting with the DAI/WETH smart contract pool. The total amount of DAI that the contract holds would be <strong>x</strong> and the total amount of WETH would be <strong>y</strong>.</p><p>Simple enough, this is the algorithm that powers a lot of AMM’s.</p><p>In the actual implementation, the formula is a bit different.</p><p>Let’s see the real formula that Uniswap V2 uses.</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/Uniswap/v2-core/blob/master/contracts/UniswapV2Pair.sol#L159">Here</a> you can find the full “swap” function implementation. But we are particularly interested in the following lines of code:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/cd5ab3592e2109958c3ff98dee7f26c11e769fbb7d5b9bd924178dcee2012419.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>This piece of code is the real implementation of the constant product formula. Everything else you have read or seen, is just the theory. These lines of code are the bare metal implementation of the constant product formula.</p><p>There are 4 variables inside the require statement:</p><ul><li><p><strong>balance0Adjusted</strong>: Reserves of <strong>x</strong> after the trader sends tokensX to the pool minus 0.3% of the amount sent.</p></li><li><p><strong>balance1Adjusted</strong>: Reserves of <strong>y</strong> after the tokensY are sent to the trader from the pool.</p></li><li><p><strong>_reserve0:</strong> Reserves of token <strong>x</strong> prior to the swap.</p></li><li><p><strong>_reserve1:</strong> Reserves of token <strong>y</strong> prior to the swap.</p></li></ul><p><strong>Of course, balance0Adjusted and balance1Adjusted can be treated inversely.</strong></p><p>Let’s do an example to understand this better:</p><ul><li><p>There is a DAI/WETH pool in Uniswap.</p></li><li><p>Current reserves are 20000DAI / 10 WETH and a trader wants to exchange 1 WETH for 1500 DAI:</p></li></ul><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/484cc015a2f27e33149632bfc7ac20eac3e94fd8d2f6765cf9f39289c1acc163.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>As you can see, the trader sent 1 WETH to the pool in exchange of 1500 DAI. He could have gotten more DAI for his WETH ! but the point here is to illustrate two things:</p><ol><li><p>The <strong>k</strong> never remains constant. It always increases due to the transaction fees, but it can also increase if people use the system inefficiently (like in our example, the trader could have gotten more DAI for his WETH).</p></li><li><p>The only check that the swap function really enforces, is that the new reserves (minus fees) are greater than or equal to previous reserves: <strong>newK ≥ oldK</strong>.</p></li></ol><p>Here is actual code implementation of the previous example:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/d6827175f6d2bea42c3f43d1511af5e2206b7264b98e9197553a59c64b453f7d.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>As you can see, things are multiplied by 1000. This is because there is no floating point numbers in Solidity, so this is necessary to represent the 0.3% fee. That is why the amount0In or amount1in are multiplied by 3 (to represent the 0.3%).</p><h2 id="h-getting-the-maximum-amount-out" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Getting the maximum amount out</h2><p>A very important thing to understand, is the maximum amount of tokens that you can get for a given input. <strong><em>NOTE:</em></strong> <em>In Uniswap V2 Periphery you have all the helper functions to implement this math.</em></p><p><strong>Example:</strong></p><p>We have a DAI /WETH pool with the following reserves:</p><ul><li><p>DAI: 100,000</p></li><li><p>WETH: 20 We want to swap 1 WETH for the maximum amount of DAI.</p></li></ul><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/660d6116e8c0f0e93df9f5d40df2936958df021b779ff3d7829f548d4251a543.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>The maximum amount of DAI that we would get for 1 WETH (for this pool) is 4748.29.</p><p>The math we just did can also be represented in code (this function is from V2 Periphery):</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/f29fd41f592ffcc5c3d25c3e9d2887e899a163dce38bfbb541575d095e2ca8d8.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>It is very important to always get this math right in order to avoid getting less output tokens.</p><p>As a final note on this topic, you maybe wondering, what determines the variable <strong>k</strong>?</p><p><strong>K</strong> is determined when the pool is first created, and then it grows every time there is a new trade. If we go to our past example, if a trader executes a swap just after the one we did, the <strong>k</strong> for his trade is going to be the <strong>newK</strong> of the prior trade.</p><h1 id="h-arbitrage" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Arbitrage</h1><p>Arbitrage is one of the most important concepts to understand how Uniswap works. Although, this concept is not unique to Uniswap, it applies to almost all of the DeFi projects. In order to understand this concept better, the first thing we need to ask ourselves is, how does Uniswap knows the price of a given token ?</p><p>For example, when you are swapping WETH for DAI in Uniswap, how does the protocol know that the price of WETH relative to DAI is <strong>x</strong> ?</p><p>The short answer is that Uniswap has no clue what the real price is. In reality, the price matches the outside world due to incentives.</p><p>The only thing that the protocol really enforces in order to make a successful swap, is the “constant product formula”.</p><p>Let’s go through an example.</p><p>Suppose there is a DAI/WETH pool in Uniswap. The price of WETH in Uniswap is trading at around 3000 DAI. We can get the price by dividing the amount of reserves of DAI by the amount of reserves of ETH (this is not the precise amount that you would get, we calculated that in the previous section).</p><p>Now, here comes the opportunity !</p><p>The price of WETH is trading at $3,200 at Coinbase. There is a difference of $200 usd between the WETH price of Uniswap and Coinbase. This will create an arbitrage opportunity, and immediately an arbitrageur (bot software) will buy WETH in Uniswap and sell it in Coinbase until the price matches.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/0f745e074e780db5ae9f591b2a8864fa9896719f7cae8ba03e77ab8ba52e0fdb.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>This is how the prices in Uniswap are correlated with the outside world, by arbitrageurs that are constantly searching for an arbitrage opportunity. There are different types of arbitrage (we will not go into detail). But for the example shown, it is not an atomic arbitrage. In other words, the risks are higher because there is a probability that when the arbitrageur tries to earn a profit in Coinbase, the prices are already at par (because another arbitrageur did it first). That is why the safest arbitrage is done on-chain, because transactions are atomic, meaning, if something fails, the whole transaction reverts (minus gas fees).</p><p>So in conclusion, Uniswap does not know about the outside world prices, the prices are almost identical to the outside world thanks to the arbitrageurs.</p><h1 id="h-impermanent-loss" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Impermanent Loss</h1><p>Impermanent loss for liquidity providers is the change in dollar terms of their total stake in a given pool at a given time vs just holding the assets.** **Let’s create an example to understand this better. Imagine that Alice has some tokens in her wallet, so she wants to become a liquidity provider to earn some yield. For our example, she is going to create a new DAI/WETH pool in Uniswap. The current market price of WETH is $3,000, so to avoid arbitrage, she will need to initiate the pool by putting 3000 DAI for every WETH. She will initiate the pool by putting 10 WETH and 30,000 DAI. With a total usd value of $60,000 (we need to keep this number in mind).</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/56c37611a02d0dd625bbf03d30a641734c1aa4ae132c0e027ea9cf2de5af457d.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>*To make it simple, we will not take into consideration the transaction fees.</p><p>Great, so now Alice became a LP of this DAI/WETH Uniswap pool. Now, let’s imagine that the price of WETH goes up to $4,687. As you should know by now, this will create a huge arbitrage opportunity, so immediately an arbitrageur will buy ETH in Alice’s pool until the price is at par with the outside market.</p><p>So after some arbitrage, Alice’s pool will look something like this (again, we are not considering the transaction fees):</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/7a0918fe49033b5150c397b6348eb600da81990b0c55ed2edfb93b1c5bbeb13e.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>As we can see, after some arbitrage now the pool has 8 WETH instead of 10 and 37,500 DAI instead of 30,000 (what Alice initially supplied). The total amount of the pool is now $75,000 ((8ETH * $4,687.5) + 37,500)).</p><p>Here comes the problem, if Alice would have just held the assets, she would have more money. Remember that initially she supplied the pool with 10 WETH and 30,000 DAI. So at the end, she had an impermanent loss of $1,875:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/0e7fc0e6869f1034cc00f9253ec08bfabac7f291f19acce7ce1473c89ed52514.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>For this example we did not contemplate the profit from transaction fees (0.3%), but the concept remains unchanged.</p><p>The reason why it is called “impermanent” is because the loss only applies if the LPs sell at the present time, if the assets fluctuate, then that loss can be mitigated.</p><p>Hopefully the concept is clear by now! If still in doubts, I highly recommend watching <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.youtube.com/watch?v=8XJ1MSTEuU0">this video</a>.</p><h1 id="h-liquidity-lp-tokens" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Liquidity / LP Tokens</h1><p>Big amounts of liquidity is what makes Uniswap an attractive system to use. Without enough liquidity, the system becomes inefficient particularly for big trades relative to the total liquidity of the pool.</p><p><strong><em>Side Note</em></strong>*: For big trades (relative to the pool), it is better to use an aggregator to decrease slippage as much as possible.*</p><p>Ok! So the first thing we need to understand is, how are LP tokens minted and burned?</p><p>When a liquidity provider provides liquidity to a pool, it receives the pool’s LP tokens proportionally to the amount of liquidity he provided.</p><p>Let’s unpack this.</p><p>A Uniswap pool is just a smart contract that “holds” certain amount of reserves (token x and y), these reserves are provided by the liquidity providers. But, this pool also has an in-house token called “LP token”. This token is unique to each pool, and the main purpose is to keep track of the liquidity each liquidity provider has injected. You can think of this token as a certificate of your liquidity. This token also accrues the 0.3% fee that Uniswap charges.</p><p>The smart contract pool imports “UniswapV2ERC20.sol” (you can find it <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/Uniswap/v2-core/blob/master/contracts/UniswapV2ERC20.sol">here</a>), which is basically a contract that has basic ERC20 functionality (minting, burning, transfer, etc..).</p><p>The LP tokens are “stored” inside of this UniswapV2ERC20.sol. Every time a liquidity provider provides liquidity, some amount of tokens are minted to his address. Inversely, every time the liquidity provider takes liquidity out, the LP tokens are burned.</p><p>So, how does this work ?</p><p>If we think about it from a high-level, a liquidity provider can <strong>a)</strong> provide liquidity and <strong>b)</strong> take out the liquidity. Uniswap implements this mechanism in two functions: <strong>mint()</strong> and <strong>burn()</strong>.</p><p>Let’s first go with the mint function, you can find the full function implementation <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/Uniswap/v2-core/blob/master/contracts/UniswapV2Pair.sol#L110">here</a>. In order to calculate the LP tokens that a liquidity provider receives, we first need to know if there is liquidity, or if it is the first time someone is providing liquidity.</p><p>Here is the piece of code (inside of the mint function) that we are interested in:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/ec62de383bc9a79881426e6fb3bc8e938297c50c226f1fe66f02d0bccf50fb86.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>The MINIMUM_LIQUIDITY is a constant variable equal to 1000:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/8cb25f6a11eae8d55b3e32291f83cb3b256bc020b71a519dea34b2532c07bf3b.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>To understand this better, we are going to go through an example.</p><p><strong><em>NOTE:</em></strong> <em>In Ethereum, the currency for computation is WEI (ETH * 10 **18), this also applies to most of the ERC-20 tokens. For simplicity, we will write the numbers without the 18 zeroes, but every time there is a hardcoded number like MAXIMUM_LIQUIDITY = 1000, we will just accommodate that number instead.</em></p><p>Let’s suppose that Alice (a liquidity provider) just created a new DAI/WETH pool.</p><ul><li><p>She will supply 10,000 DAI and 2 WETH:</p></li></ul><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/d63041c2697c107b0bdcd1d00c0eb2ed45cc27e6c12cc2ced74601e572f71391.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>Before Alice, the total supply of the pool was 0, so the first thing that is going to happen is mint 1000 tokens to the address 0 (this is done to maintain a minimum liquidity):</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/3c56af116675d5acf074b969a75d73e1b1c637976b9a15d3bb487ebbfc9840f5.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p><strong>Note:</strong> <em>Every time there is a mint, the totalSupply variable gets updated</em>:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/f21b9b7d22db4b9e80cda7e7eaa312dd9f42f8eed0f79b682f256d38a400a489.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>We then get the amount of LP tokens that are going to be minted to the liquidity provider (Alice), for our example, the result was 141.42.. Again, this is the formula to determine that:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/20746fad3281e6d84fd78d2aa644aa3ddeab02844bd134b39168fa950cfd2672.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>Great, so now our pool consists of the following:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/270fc1c786fb1e6af607b9a8b42ed6e9043d43fc51ae43a44697522e6e077a24.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>Total supply means the total amount of LP tokens there are in circulation (do not confuse it with the <strong>x</strong> and <strong>y</strong> reserves).</p><p>Now, let’s imagine that Alice wants to withdraw every DAI and WETH she putted into the pool. For the sake of simplicity, let’s suppose that the reserves remain the same.</p><p>In order for Alice to remove her liquidity, she needs to call the burn function, but prior to that, she needs to transfer all her LP tokens to the pool’s address. Let’s understand how this works, <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/Uniswap/v2-core/blob/master/contracts/UniswapV2Pair.sol#L134">here</a> is the complete burn function implementation.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/bc1bce4dbba5a7ae4943db12a3e8d74360afb678691b8483c1ecac07e1b357b9.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>If we see line 187, the variable <strong><em>liquidity</em></strong> is the amount of LP tokens that the contract’s address hold. Hence, that is why I mentioned that Alice needs to transfer the LP tokens prior to the function call. Then, in line 191 and 192 are the amounts of token <strong>x</strong> and <strong>y</strong> to be transfered to Alice.</p><p>Following our example, let’s unpack this:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/0c8db0b64c9322f61d757ecfb208f0b3b4f4eac189dd0c4b2f507d4c9ea5724e.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>As you can see, amount0 = 10000 and amount1 = 2, that is the initial amount that Alice supplied !</p><p>After the amounts are calculated, they are transferred and then burned.</p><p>This is the magic behind LP tokens !</p><p>As a final note on this topic, if we remember, the protocol charges a 0.3% trading fee. This fee goes to the liquidity providers. The way the fee is technically accrued, is by increasing the reserves of <strong>x</strong> and <strong>y in</strong> every trade. This will positively impact the amount of LP tokens each liquidity provider holds (proportionally to their holdings).</p><p>If we go back to the burn function, these 2 lines of code are what calculate the amount of token <strong>x</strong> and <strong>y</strong> to send to the liquidity provider when he wants to exchange his LP tokens for the <strong>x</strong> and <strong>y</strong> tokens.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/305125a65d3f15a16623c1b8ddf1264f05f841c02373d827b4c797d662d4f0e5.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>Every time a trader executes a swap, it needs to send a buffer of 0.3% relative to the size of the trade, that 0.3% increments the reserves of the pool. Therefore, increasing the value of <strong><em>balance0</em></strong> and <strong><em>balance1</em></strong> in the burn function.</p><h1 id="h-systems-core-implementation" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">System’s CORE Implementation</h1><p>Now that we have a solid understanding of the individual components, let’s go to the actual implementation. This part will be much faster, as the core topics were already covered.</p><h2 id="h-protocol-architecture" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Protocol Architecture</h2><p><strong><em>Uniswap V2 is a binary smart contract system,</em></strong> it is composed of the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/Uniswap/v2-core">V2-Core</a> and <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/Uniswap/v2-periphery">V2-Periphery</a>.</p><ul><li><p><strong><em>V2-Core</em></strong> <em>are the low-level base contracts. These smart contracts are responsible for the system’s functionality.</em></p></li><li><p><strong><em>V2-Periphery</em></strong> <em>are helper contracts that allow front-end applications and developers integrate with the core contracts, by applying safety checks and abstracting away certain things.</em></p></li></ul><p>In simpler terms, V2-Core is the part of the protocol that implements the core features (swapping, minting, burning etc..). In contrastV2-Periphery is one layer up. It is a set of contracts and libraries that make the integration easier for the developers.</p><p>The Uniswap team also architected this modular codebase approach to reduce security critical vulnerabilities by having the bare minimum in V2-Core. In other words, they only implemented what is necessary in V2-Core so there is less code to audit (therefore more secure). All the helper functions were thrown to V2-Periphery.</p><p>We will only focus on V2-Core. The simple reason, is that if you know how V2-Core works, by default you also know how V2-Periphery works (but not the other way around).</p><p>Again, you can find the V2-Core repo <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/Uniswap/v2-core/tree/master/contracts">here</a>.</p><p>There are 3 main contracts:</p><ol><li><p><strong>UniswapV2ERC20.sol:</strong> This contract is responsible for handling the LP tokens. It is a basic ERC20 contract, so we will not go over this.</p></li><li><p><strong>UniswapV2Factory.sol:</strong> This is the factory that is responsible for deploying new pools (pairs) and also to keep track of them.</p></li><li><p><strong>UniswapV2Pair.sol:</strong> This is where the action happens (the pool implementation).</p></li></ol><h2 id="h-uniswap-v2-factory" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Uniswap V2 Factory</h2><p>Again, the factory contract is mainly responsible for creating new contract pairs (UniswapV2Pair.sol), <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://etherscan.io/address/0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f">here is the V2 factory contract on Etherscan</a>.</p><p>In order to concentrate liquidity, there can only be one smart contract per pair. In other words, if there is a WETH/UNI pair contract already, the factory won’t allow to create the same pair. Of course, you can bypass that (by deploying the pair contract directly), but the core principle here is to concentrate liquidity as much as possible to avoid price slippage and have more liquidity.</p><p>Here is the function that creates pairs in UniswapV2Factory:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/be227c484ef70d05c31085a217f93f67a1ecd84d45348df899624cab80fb4e0e.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>One thing that probably was still in question is, what determines the order of <strong>x</strong> and <strong>y</strong> ? The pairs are grouped by hexadecimal order as you can see in line 25:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/586371bf6b53a20b6d6e5675fb98866156ade6176357825c769b9608be597f20.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>In line 27 it checks that the pair is unique:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/1bf274e3e4129dcd60dbf1059d5681602795816a65e0072d1ccf3341246663ca.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>In line 30-32 it creates the pair (UniswapV2Pair.sol) using create2:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/96a7f7b14ca72db29754a06d94dc96f40bfb47661fbe55ccc5b907da7b681344.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>Then it initializes the contract with the address of each token and adds them to an array and mapping.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/a886924e34f08dcf75b0d937e9fbce72eb22de8d411be0b4fea9585e2afa5b1b.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>Another important point is that the factory can turn on a fee to charge a percentage per swap(you can check that yourself, it is pretty trivial).</p><p>That’s it for the factory. It is a very simple and straight-forward factory implementation, nothing complicated.</p><h2 id="h-uniswap-v2-pair" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Uniswap V2 Pair</h2><p><strong>UniswapV2Pair.sol</strong> is the foundation of UniswapV2, <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/Uniswap/v2-core/blob/master/contracts/UniswapV2Pair.sol">here is the contract.</a></p><p>This contract is responsible for handling unique pools (each pool = one contract).</p><p>The basic functionality of this contract is to <strong>swap</strong>, <strong>mint</strong> and <strong>burn</strong>. We already went in detail as to how this components work, so we will just give them one quick brush.</p><p><strong>Swap()</strong>: The swap function is the star of Uniswap. It is the function that gets called every time you want to trade one token for another. You can see the full function implementation <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/Uniswap/v2-core/blob/master/contracts/UniswapV2Pair.sol#L159">here</a>. The basic task of this function is to enforce that <strong>newK</strong> is greater than or equal to <strong>k</strong>:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/a8ecd071d104aeb029d61114fcc6f36524c2253a7952d741b60cb02e23ebc2c3.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p><strong>Mint():</strong> The mint function is responsible for minting LP tokens every time a liquidity provider provides liquidity, you can find it <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/Uniswap/v2-core/blob/master/contracts/UniswapV2Pair.sol#L110">here</a>.</p><p><strong>Burn():</strong> The burn function is responsible of burning LP tokens every time a liquidity provider wants to take his assets out of the pool, you can find it <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/Uniswap/v2-core/blob/master/contracts/UniswapV2Pair.sol#L134">here</a>.</p><h2 id="h-note-on-flash-loans" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Note on Flash Loans</h2><p>Flash loans are a very important topic that we did not cover in this post. The reason why is because we are going to cover it in depth in the next post about AAVE.</p><p>Hopefully you enjoyed it !</p><p>Any comments are kindly welcomed.</p><p>References:</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://drive.google.com/viewerng/viewer?url=https://uniswap.org/whitepaper.pdf">https://drive.google.com/viewerng/viewer?url=https://uniswap.org/whitepaper.pdf</a></p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://drive.google.com/viewerng/viewer?url=https://uniswap.org/whitepaper.pdf">https://drive.google.com/viewerng/viewer?url=https://uniswap.org/whitepaper.pdf</a></p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.uniswap.org/protocol/V2/concepts/protocol-overview/how-uniswap-works">https://docs.uniswap.org/protocol/V2/concepts/protocol-overview/how-uniswap-works</a></p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.uniswap.org/protocol/V2/concepts/protocol-overview/how-uniswap-works">https://docs.uniswap.org/protocol/V2/concepts/protocol-overview/how-uniswap-works</a></p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.uniswap.org/protocol/V2/concepts/protocol-overview/how-uniswap-works">https://docs.uniswap.org/protocol/V2/concepts/protocol-overview/how-uniswap-works</a></p>]]></content:encoded>
            <author>rodrigo-herrera-itie@newsletter.paragraph.com (Rodrigo Herrera Itie)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/a9fc3dabebd6dfd03cdc7cd5422e503de86f503fd3f8d99cb91b7901f9daccb9.png" length="0" type="image/png"/>
        </item>
    </channel>
</rss>