Aprendizaje de contrato periférico UniswapV2 (7) - ExampleSlidingWindowOracle.sol

Recuerdo una frase en el círculo de amigos, si Defi es la corona de Ethereum, entonces Uniswap es la joya de esta corona. Uniswap es actualmente la versión V2. En comparación con la V1, sus funciones están más optimizadas, pero su código fuente de contrato no es complicado. Este artículo es una serie de artículos de registro para el aprendizaje personal del código fuente UniswapV2.

1. Introducción al contrato ExampleSlidingWindowOracle

Este contrato es el mismo que el ExampleOracleSimplecontrato aprendido en el artículo anterior , utilizando UniswapV2 como la máquina de oráculo de precios. Pero los dos escenarios de aplicación son diferentes:

  • ExampleOracleSimpleEl contrato se utiliza en un modo de ventana fija, en el que los datos históricos no son importantes y el precio actual tiene el mismo peso que el precio histórico. Por lo tanto, es suficiente registrar (actualizar) el precio promedio una vez por ciclo.
  • ExampleSlidingWindowOracleUtilizado en el modo de ventana deslizante, puede registrar información relacionada con el precio varias veces en un ciclo. El modo de ventana deslizante también se divide en dos categorías, una es la media móvil simple, lo que significa que cada cálculo de precio tiene el mismo peso. El otro es el promedio móvil exponencial, el cálculo de precio más reciente tiene un valor de peso mayor.

Este contrato es un ejemplo de implementación de media móvil simple. Para obtener más información sobre el uso de UniswapV2 como una máquina de oráculo de precios, lea su documento Comprando un Oracle . Este documento también explica los problemas de desbordamiento en los cálculos de precios.

2. Código fuente del contrato

pragma solidity =0.6.6;

import '@uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol';
import '@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol';
import '@uniswap/lib/contracts/libraries/FixedPoint.sol';

import '../libraries/SafeMath.sol';
import '../libraries/UniswapV2Library.sol';
import '../libraries/UniswapV2OracleLibrary.sol';

// sliding window oracle that uses observations collected over a window to provide moving price averages in the past
// `windowSize` with a precision of `windowSize / granularity`
// note this is a singleton oracle and only needs to be deployed once per desired parameters, which
// differs from the simple oracle which must be deployed once per pair.
contract ExampleSlidingWindowOracle {
    
    
    using FixedPoint for *;
    using SafeMath for uint;

    struct Observation {
    
    
        uint timestamp;
        uint price0Cumulative;
        uint price1Cumulative;
    }

    address public immutable factory;
    // the desired amount of time over which the moving average should be computed, e.g. 24 hours
    uint public immutable windowSize;
    // the number of observations stored for each pair, i.e. how many price observations are stored for the window.
    // as granularity increases from 1, more frequent updates are needed, but moving averages become more precise.
    // averages are computed over intervals with sizes in the range:
    //   [windowSize - (windowSize / granularity) * 2, windowSize]
    // e.g. if the window size is 24 hours, and the granularity is 24, the oracle will return the average price for
    //   the period:
    //   [now - [22 hours, 24 hours], now]
    uint8 public immutable granularity;
    // this is redundant with granularity and windowSize, but stored for gas savings & informational purposes.
    uint public immutable periodSize;

    // mapping from pair address to a list of price observations of that pair
    mapping(address => Observation[]) public pairObservations;

    constructor(address factory_, uint windowSize_, uint8 granularity_) public {
    
    
        require(granularity_ > 1, 'SlidingWindowOracle: GRANULARITY');
        require(
            (periodSize = windowSize_ / granularity_) * granularity_ == windowSize_,
            'SlidingWindowOracle: WINDOW_NOT_EVENLY_DIVISIBLE'
        );
        factory = factory_;
        windowSize = windowSize_;
        granularity = granularity_;
    }

    // returns the index of the observation corresponding to the given timestamp
    function observationIndexOf(uint timestamp) public view returns (uint8 index) {
    
    
        uint epochPeriod = timestamp / periodSize;
        return uint8(epochPeriod % granularity);
    }

    // returns the observation from the oldest epoch (at the beginning of the window) relative to the current time
    function getFirstObservationInWindow(address pair) private view returns (Observation storage firstObservation) {
    
    
        uint8 observationIndex = observationIndexOf(block.timestamp);
        // no overflow issue. if observationIndex + 1 overflows, result is still zero.
        uint8 firstObservationIndex = (observationIndex + 1) % granularity;
        firstObservation = pairObservations[pair][firstObservationIndex];
    }

    // update the cumulative price for the observation at the current timestamp. each observation is updated at most
    // once per epoch period.
    function update(address tokenA, address tokenB) external {
    
    
        address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);

        // populate the array with empty observations (first call only)
        for (uint i = pairObservations[pair].length; i < granularity; i++) {
    
    
            pairObservations[pair].push();
        }

        // get the observation for the current period
        uint8 observationIndex = observationIndexOf(block.timestamp);
        Observation storage observation = pairObservations[pair][observationIndex];

        // we only want to commit updates once per period (i.e. windowSize / granularity)
        uint timeElapsed = block.timestamp - observation.timestamp;
        if (timeElapsed > periodSize) {
    
    
            (uint price0Cumulative, uint price1Cumulative,) = UniswapV2OracleLibrary.currentCumulativePrices(pair);
            observation.timestamp = block.timestamp;
            observation.price0Cumulative = price0Cumulative;
            observation.price1Cumulative = price1Cumulative;
        }
    }

    // given the cumulative prices of the start and end of a period, and the length of the period, compute the average
    // price in terms of how much amount out is received for the amount in
    function computeAmountOut(
        uint priceCumulativeStart, uint priceCumulativeEnd,
        uint timeElapsed, uint amountIn
    ) private pure returns (uint amountOut) {
    
    
        // overflow is desired.
        FixedPoint.uq112x112 memory priceAverage = FixedPoint.uq112x112(
            uint224((priceCumulativeEnd - priceCumulativeStart) / timeElapsed)
        );
        amountOut = priceAverage.mul(amountIn).decode144();
    }

    // returns the amount out corresponding to the amount in for a given token using the moving average over the time
    // range [now - [windowSize, windowSize - periodSize * 2], now]
    // update must have been called for the bucket corresponding to timestamp `now - windowSize`
    function consult(address tokenIn, uint amountIn, address tokenOut) external view returns (uint amountOut) {
    
    
        address pair = UniswapV2Library.pairFor(factory, tokenIn, tokenOut);
        Observation storage firstObservation = getFirstObservationInWindow(pair);

        uint timeElapsed = block.timestamp - firstObservation.timestamp;
        require(timeElapsed <= windowSize, 'SlidingWindowOracle: MISSING_HISTORICAL_OBSERVATION');
        // should never happen.
        require(timeElapsed >= windowSize - periodSize * 2, 'SlidingWindowOracle: UNEXPECTED_TIME_ELAPSED');

        (uint price0Cumulative, uint price1Cumulative,) = UniswapV2OracleLibrary.currentCumulativePrices(pair);
        (address token0,) = UniswapV2Library.sortTokens(tokenIn, tokenOut);

        if (token0 == tokenIn) {
    
    
            return computeAmountOut(firstObservation.price0Cumulative, price0Cumulative, timeElapsed, amountIn);
        } else {
    
    
            return computeAmountOut(firstObservation.price1Cumulative, price1Cumulative, timeElapsed, amountIn);
        }
    }
}

Tres, un breve estudio del código fuente

Estudiemos brevemente el código fuente de este contrato. Es posible que la comprensión personal no sea correcta, deje un mensaje para corregirme.

  • Versión de solidez en la primera línea

  • Las líneas 2-7 importan 6 definiciones de interfaz o bibliotecas de herramientas, que es solo una más que el artículo anterior SafeMath.

  • A continuación está la nota del contrato (descripción), que revela varios puntos clave:

    1. La ventana deslizante adopta el modo de observador. El tamaño de la ventana de observación (tiempo) es windowSize, y la precisión es windowSize / granularity. El granularityvalor literal aquí es la granularidad, que en realidad significa el escenario. Se asume aquí que windowSizeson 24 horas, es decir, la ventana de observación es 24 horas. La granularidad es 8, luego la precisión es 3 horas, es decir, el precio promedio se puede registrar 8 veces en un ciclo, por lo que es más fácil ver la tendencia del precio.
    2. Para los parámetros fijos, este contrato solo debe implementarse una vez, que es un contrato único. El modo de ventana fija del artículo anterior requiere que se implemente un contrato para cada par comercial.
  • La definición del contrato y las siguientes dos usinggramáticas se han estudiado muchas veces, y también son muy simples, así que omítalas directamente.

  • struct Observation {
          
          
        uint timestamp;
        uint price0Cumulative;
        uint price1Cumulative;
    }
    

    Define una estructura llamada observador. Tiene tres campos: el primer campo registra el tiempo de bloque durante la observación y los dos últimos campos registran el valor acumulado del precio durante la observación.

  • Lo siguiente es la definición de cuatro variables de estado, respectivamente factory,windowSize,granularity,periodSize. Sus significados son la factorydirección del contrato de V2 , el tamaño de la ventana de observación, la granularidad y la precisión de la ventana de observación (es decir, el tamaño de la ventana dividido por la granularidad). Las notas aquí también mencionan una gran cantidad de contenido, como cuanto mayor es la granularidad, más frecuente es la actualización y más preciso es el precio promedio móvil. Allí se mencionan, podrían haber sido revisadas granularityy windowSizecalculadas periodSize, pero con el fin de ahorrar de forma más intuitiva y Gas, también se registra como una variable de estado.

  • pairObservationsUtilice un mapa para registrar los observadores de cada par comercial. El observador es una matriz y su longitud es granularidad, que representa el número de observaciones.

  • El siguiente es el constructor.

    • Primero, verifique que la granularidad no pueda ser 0, porque es un divisor. Aunque dividir por cero sin verificación también informará una transacción de restablecimiento de error, el requiresignificado es más claro.
    • Luego verifique que la ventana de observación pueda ser divisible por la granularidad y periodSizeasigne valores al mismo tiempo . Esto es obvio, de lo contrario habrá un espacio en la ventana de observación. Observe la sintaxis aquí: el (periodSize = windowSize_ / granularity_) * granularity_ == windowSize_,extremo izquierdo de la ecuación usa una expresión como multiplicador. Esto no es compatible con algunos lenguajes de programación y las expresiones no se pueden mezclar con valores. Pero en JavaScriptuna serie de idiomas admitidos, el valor del lado izquierdo de la expresión es el valor de la expresión. Aunque esta sintaxis rara vez se usa en Solidity, muestra que también es compatible.
    • A continuación, establezca el valor de la variable de estado previamente definida (parámetro de observación). Tenga en cuenta que granularityes de uint8tipo, es decir, un contenedor puede grabar hasta 255 veces, lo cual es suficiente.
  • Función observationIndexOfpara obtener el índice de observador en un momento dado. Primero divide por periodSizepara obtener el resto, asumiendo que se llama E (es decir, cuántas precisiones contiene). Debido granularityal uint8tamaño, es imposible registrar todos los datos E. Así que solo toma el módulo para reciclar. Dado que una operación uintAND uint8sigue siendo una uint, es necesario convertirla a por fin uint8.

  • getFirstObservationInWindowLa función es una función privada. El comentario mencionado es el primer observador en obtener la nueva ventana actual. Su índice suma 1 al índice del registro de bloque actual.

    ¿Por qué agregar 1? Porque el observador es cíclico. Si el último índice se incrementa en 1, entonces su posición está vacía o tiene un valor antiguo. Tener un valor antiguo equivale a volver al comienzo de un período de ventana. Esta función se utiliza en cálculos posteriores, de modo que el tiempo de bloque actual menos el tiempo de inicio del período de ventana cuando se calcula es exactamente un período de ventana.

    Para evitar el desbordamiento, se adopta el método de módulo, que por supuesto es equivalente a la conversión directa de tipos. Esto también se menciona en el aprendizaje de pares de negociación de contratos básicos. Lo último a tener en cuenta es que, debido a que es una función privada, se usa internamente. Por tanto storage, Observationse devuelve una variable del tipo, de modo que la referencia se pasará al pasar, evitando la sobrecarga de copiar el objeto.

  • updatefunción. Actualiza el precio acumulado del observador de bloque actual. Se menciona en los comentarios que cada period(precisión) se actualiza como máximo una vez. Los parámetros de la función son las dos direcciones de token del par comercial.

    • Primero use la biblioteca de herramientas UniswapV2 para calcular la dirección del par comercial.
    • El siguiente es un forbucle, si la matriz de observadores del par comercial no se inicializa en este momento, se inicializa con datos vacíos. La longitud de la matriz es la granularitymisma después de la inicialización , por lo que no se inicializará una segunda vez.
    • Las siguientes dos líneas de código obtienen la información del observador registrada en el bloque actual.
    • A continuación, se *uint* timeElapsed = *block*.timestamp - observation.timestamp;utiliza para calcular la diferencia entre el tiempo de bloqueo actual y el tiempo registrado por el observador actual (el tiempo registrado actual también puede ser 0, es decir, no se ha registrado).
    • La siguiente es una iforación para determinar si la diferencia de tiempo es mayor que la precisión especificada (registre como máximo una vez dentro de una precisión). Si se cumplen las condiciones, la biblioteca de herramientas UniswapV2 se utiliza para calcular el precio acumulativo del bloque actual y actualizar el registro del observador actual. Esto actualiza el valor acumulado del precio del observador del bloque actual y el tiempo del bloque (si se cumple el requisito de intervalo de tiempo). Tenga en cuenta aquí: los observadores se reciclan y los nuevos sobrescribirán a los antiguos.
  • computeAmountOutfunción. También es una función privada que utiliza el precio medio para calcular la cantidad de un determinado activo. Tenga en cuenta que el método de cálculo del precio medio es el mismo que se menciona en el artículo anterior, es decir, la diferencia de precio acumulada dividida por el intervalo de tiempo (similar a la fórmula para calcular la velocidad media).

  • consultFunción de consulta. Basado en el precio promedio durante toda la ventana, dada la cantidad de un token, calcule la cantidad de otro token. Sus parámetros son la dirección y la cantidad del token de entrada y la dirección del token que se va a calcular.

    • En los comentarios de la función se menciona mucha información, como el intervalo de tiempo del precio medio utilizado en la consulta. Además, el período de tiempo correspondiente debe tener precios actualizados.
    • La primera línea de la función se utiliza para calcular la dirección del par comercial.
    • La segunda línea obtiene el primer observador de la nueva ventana.
    • La tercera línea calcula la diferencia entre el tiempo de bloque actual y el tiempo registrado por el primer observador en la nueva ventana.
    • La cuarta línea verifica que la diferencia de tiempo debe ser menor que un período de ventana, es decir, no se puede actualizar por mucho tiempo.
    • La quinta línea se utiliza para verificar el límite inferior de la diferencia horaria.
    • La sexta línea se utiliza para obtener la diferencia de precio acumulada del bloque actual.
    • La séptima línea se utiliza para ordenar las dos direcciones de token de los parámetros de entrada.
    • La función finalmente calcula el número de tokens según si la entrada está token0quieta token1. ¿Por qué utilizar el primer cálculo de índice de la nueva ventana aquí? Consulte la getFirstObservationInWindowdescripción de la función.

Cuatro, resumen

Nota: Aunque la precisión (etapas) se divide de acuerdo con la granularidad en el período de la ventana, cada etapa registra el tiempo de bloque del observador y el precio acumulado en ese momento. Su función es reflejar el deslizamiento del precio y la otra no es utilizar la misma acumulación. Puntos de precio (no fijos, en relación con los puntos de precio acumulados fijos del artículo anterior) para calcular el precio medio. Sin embargo, el precio promedio todavía se calcula durante todo el período de ventana, no un precio promedio con precisión.

Durante cada consulta, el período de ventana de la consulta se perioddeslizará una cuadrícula de arriba a la derecha (una granularidad), por lo que se denomina ventana deslizante vívidamente.

Cada máquina debe utilizar dichas predicciones, perioddebe actualizarse el valor del precio acumulado, ad infinitum; de lo contrario, la posición inicial de la ventana durante este periodtiempo, habrá circunstancias en que el intervalo de consulta sea mayor que durante la ventana, lo que provocará que la consulta falle. Pero siempre que periodla información del observador se actualice nuevamente , la consulta se puede reanudar.

Bueno, este estudio ha terminado, la próxima vez que planeo estudiar examplesbajo el catálogo ExampleSwapToPrice.sol.

Debido a la capacidad personal limitada, es inevitable que haya errores o entendimientos incorrectos. Deje un mensaje para corregirlo.

Supongo que te gusta

Origin blog.csdn.net/weixin_39430411/article/details/109262196
Recomendado
Clasificación