DEV Community

Cover image for Esta fórmula mueve billones 💰 en DeFi
Ahmed Castro
Ahmed Castro

Posted on

Esta fórmula mueve billones 💰 en DeFi

¿Te has preguntado cómo funcionan los proyectos DeFi en el fondo? En este video exploramos el funcionamiento de una de mis mecánicas favoritas: el rebase, también conocido como reflexiones. Esta mecánica se ha mantenido muy vigente pues inició como cualquier otro liquidity farming y ahora es parte fundamental de los LSDs o LSTs, tokens de staking líquido en ethereum que a la fecha tienen un TVL mayor a 28 billones. Personalmente, he lanzado tokens con rebase así que en este video te explico cómo lo hice. También haremos ejercicios prácticos que puedes ir siguiendo paso a paso para consolidar tus conocimientos.

¿Qué necesitas para este video?

En este video ocupas conocimientos sobre programación, matemáticas y finanzas. Si sientes debilidad en cualquiera de las tres no te preocupes, estaré explicando de manera sencilla y con ejemplos. Y si tienes dudas déjame un comentario.

Para este tutorial necesitarás Metamask, o cualquier otra wallet de tu preferencia, con fondos en Scroll Sepolia que puedes obtener desde la Sepolia faucet y luego los puedes bridgear a L2 usando el Scroll Sepolia bridge. También puedes usar una Scroll Sepolia Faucet para recibir fondos directamente en L2.

En esta guía lanzaremos en Scroll Sepolia. También explicaré los cambios exactos que debes realizar para lanzar en cualquier otra chain EVM.

1. El problema del rebase en Ethereum

Antes de entrar en detalle de la fórmula del rebase es necesario entender qué sucede si no la usamos. A continuación una fórmula y un token en Solidity que implementa el rebase de una forma demasiado ingenua y simple. ¿Puedes ver porqué no quisiéramos implementar un token de esta manera?

i=0totalAccounts1balance[i]+=rebaseAmount×sharesOf(i)totalShares \sum_{i=0}^{\text{totalAccounts}-1} \text{balance}[i] += \text{rebaseAmount} \times \frac{\text{sharesOf}(i)}{\text{totalShares}}

// SPDX-License-Identifier: MIT

pragma solidity 0.8.23;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

// NO USAR EN PRODUCCION: solo para fines educativos
contract MyToken is ERC20
{    
    uint public totalAccounts = 0;
    mapping(uint accountId => address account) allAccounts;

    constructor () ERC20("My Token", "TKN")
    {
        _mint(msg.sender, 1_000_000 ether);
        _addToAllAccounts(msg.sender);
    }

    function transfer(address to, uint256 value) public override returns (bool) {
        _addToAllAccounts(to);
        return super.transfer(to, value);
    }

    function transferFrom(address from, address to, uint256 value) public override returns (bool) {
        _addToAllAccounts(to);
        return super.transferFrom(from, to, value);
    }

    function _addToAllAccounts(address account) internal {
        bool accountAlreadyExists = false;
        for(uint i=0; i<totalAccounts; i++)
        {
            if(allAccounts[i] == account)
            {
                accountAlreadyExists = true;
                break;
            }
        }
        if(!accountAlreadyExists)
        {
            allAccounts[totalAccounts] = account;
            totalAccounts += 1;
        }
    }

    function rebase(uint amount) public {
        for(uint i=0; i<totalAccounts; i++)
        {
            if(allAccounts[i] != msg.sender)
            {
                uint recipientShares = (balanceOf(allAccounts[i]) * 1 ether) / totalSupply();
                super.transferFrom(msg.sender, allAccounts[i], (amount * recipientShares) / 1 ether);
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

2. Rebase usando una fórmula optimizada

Como observamos en el primer ejemplo, necesitamos pensar en algo que sea sostenible en términos de gas. Para resolver este problema utilizamos la siguiente fórmula que es más elegante y eficiente.

balanceOf(x)=sharesOf(x)×RATE \text{balanceOf}(x) = \text{sharesOf}(x) \times{\text{RATE}}
// SPDX-License-Identifier: MIT

pragma solidity 0.8.23;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

// NO USAR EN PRODUCCION: solo para fines educativos
contract MyToken is ERC20
{    
    uint public RATE = 1 ether;

    constructor () ERC20("My Token", "TKN")
    {
        _mint(msg.sender, 1_000_000 ether);
    }

    function balanceOf(address account) public view override returns (uint256) {
        return (super.balanceOf(account) * RATE) / 1 ether;
    }

    function totalSupply() public view override returns (uint256) {
        return (super.totalSupply() * RATE) / 1 ether;
    }

    /*
      Nota: Este codigo no toma en cuenta el RATE al momento de transferencias futuras
      Para hacerlo se debera implementar haciendo override de transfer y transferFrom
    */

    function rebase(uint value) public {
        uint oldSupply = totalSupply();
        _burn(msg.sender, value);
        RATE = (oldSupply * 1 ether) / totalSupply();
    }
}
Enter fullscreen mode Exit fullscreen mode

3. Combinación de mecánicas: Fees con Auto Rebase

El rebase es una mecánica bastante flexible. Ahora que ya comprendemos la fórmula, la podemos combinar con con otras mecánicas. En este caso la combinaremos con comisiones de transacción donde un 10% de cada transacción será distribuido a todos los holders.

// SPDX-License-Identifier: MIT

pragma solidity 0.8.23;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

// NO USAR EN PRODUCCION: solo para fines educativos
contract MyToken is ERC20
{    
    uint public FEE_PERCENTAGE = 1000; // 10%
    uint public FEE_DECIMALS = 2;
    uint public RATE = 1 ether;

    constructor () ERC20("My Token", "TKN")
    {
        _mint(msg.sender, 1_000_000 ether);
    }

    function balanceOf(address account) public view override returns (uint256) {
        return (super.balanceOf(account) * RATE) / 1 ether;
    }

    function totalSupply() public view override returns (uint256) {
        return (super.totalSupply() * RATE) / 1 ether;
    }

    function transfer(address to, uint256 value) public override autoRebase(value) returns (bool) {
        // Nota: Este codigo no toma en cuenta el cambio de RATE para calcular el balance a transferir
        uint fee = calculateFee(value);
        _burn(msg.sender, fee);
        return super.transfer(to, value - fee);
    }

    function transferFrom(address from, address to, uint256 value) public override autoRebase(value) returns (bool) {
        // Nota: Este codigo no toma en cuenta el cambio de RATE para calcular el balance a transferir
        uint fee = calculateFee(value);
        _burn(from, fee);
        return super.transferFrom(from, to, value - fee);
    }

    modifier autoRebase(uint value) {
        uint oldSupply = totalSupply();
        _;
        RATE = (oldSupply * 1 ether) / totalSupply();
    }

    function calculateFee(uint256 value) internal view returns(uint256) {
        return (value * FEE_PERCENTAGE) / (10**(FEE_DECIMALS + 2));
    }
}
Enter fullscreen mode Exit fullscreen mode

4. Ejemplos completos

Si quieres lanzar un token de rebase te recomendaría inspirarte en o forkear un proyecto ya lanzado y auditado. Exploremos un par.

  • Ampleforth, uno de los OGs
    • Muy similar a nuestra implementación
    • Usa una división en vez de una multiplicación (mismo efecto)
    • DIGG de Badger y sOHM de Olympus forkearon Ampleforth
  • Safemoon es un famoso ejemplo de fees con auto rebase.
  • Lido hace rebase con la fórmula a continuación:
balanceOf(x)=sharesOf(x)×stakedETHtotalShares \text{balanceOf}(x) = \text{sharesOf}(x) \times \frac{\text{stakedETH}}{\text{totalShares}}

¡Gracias por ver este tutorial!

Sígueme en dev.to y en Youtube para todo lo relacionado al desarrollo en Blockchain en Español.

Top comments (0)