Actualizado el 15 de septiembre de 2023: Balancer ha publicado el post-mortem oficial, que proporciona una descripción detallada de toda la historia de este incidente, incluyendo la experiencia y las lecciones aprendidas. Este post-mortem, con su narrativa intrincada y excelente, es convincente y sin duda merece su atención.
Desde una perspectiva de seguridad, este post-mortem revela que existen dos errores. El primero es el error de redondeo hacia abajo que hemos discutido en nuestro informe, y el segundo es el "restablecimiento de la tasa con suministro 0", que ocurrió en los pasos de ataque 3.6 y 3.7, tal como se describe en nuestro informe. El informe de Balancer considera el segundo como el problema más crítico, siendo el primero un factor contribuyente. Sin embargo, creemos que ambos errores son igualmente importantes para una explotación rentable:
El primer error se utiliza para inflar la tasa del token, siendo la causa raíz del beneficio. Sin él, generar beneficio sería inviable.
El segundo error permite el exploit al equilibrar la deuda de los bb-a-tokens. Sin él, el ataque fallaría debido a la escasa liquidez de los bb-a-tokens, dado que no hay otras fuentes para obtener estos tokens (a menos que el atacante logre conseguirlos por algún medio).
El 22 de agosto de 2023, Balancer anunció públicamente la presencia de una vulnerabilidad crítica que afectaba a múltiples pools con apalancamiento, e instó a los usuarios a retirar sus LP de los pools afectados de inmediato. Balancer había iniciado procedimientos de mitigación de emergencia para asegurar la mayor parte del TVL, aunque algunos fondos permanecían en riesgo. Desafortunadamente, el 27 de agosto, cinco días después, observamos varios ataques en la práctica. Desde entonces, se han robado activos por valor de más de $2.12M.
En el momento de redactar este informe (más de tres semanas después del anuncio, en un momento en que consideramos que es seguro hacerlo), Balancer no ha publicado ningún análisis en profundidad de esta vulnerabilidad. En este informe, nuestro objetivo es proporcionar un análisis exhaustivo, basado principalmente en una de las transacciones de ataque.
Conclusiones clave (TL;DR)
- Nuestra investigación indica que la causa raíz proviene de la manipulación de precios resultante de la lógica de redondeo hacia abajo en el pool
linear. Esto, a su vez, afecta de manera inapropiada a la tasa de token en caché utilizada por el poolboostedcorrespondiente. - Este incidente enfatiza la necesidad crítica de notificaciones oportunas a los proyectos que han bifurcado desde una fuente vulnerable, lo que representa un desafío significativo para toda la comunidad.
- Los numerosos ataques en curso subrayan la necesidad de una prevención proactiva de amenazas, lo que inevitablemente podría ayudar a mitigar pérdidas futuras.
En las siguientes secciones, primero proporcionaremos información de contexto esencial sobre Balancer. A continuación, realizaremos un análisis exhaustivo de la vulnerabilidad y el ataque asociado. Finalmente, proporcionaremos un breve resumen de los ataques que hemos observado hasta ahora, junto con sus beneficios correspondientes.
0x1 Antecedentes sobre Balancer
Balancer V2 [1] es un protocolo de creador de mercado automatizado (AMM) descentralizado que representa un bloque de construcción flexible para la liquidez programable. A diferencia de otros AMM donde la contabilidad de tokens está emparejada con la lógica del pool, Balancer separa la contabilidad y gestión de tokens de la lógica del pool, lo que puede mejorar la eficiencia de los intercambios al reducir muchas transferencias de tokens.
Balancer admite varios tipos de pools. Cada pool está asociado con un token LP denominado BPT (es decir, Balancer Pool Token). Básicamente, el valor del BPT se calcula en función del valor total de todos los tokens subyacentes.
Balancer admite intercambios de múltiples saltos, también conocidos como batch swaps, que aprovechan los mejores precios de todos los pools registrados en el Vault. En concreto, el Vault proporciona la función batchSwap para facilitar los intercambios de múltiples saltos.
Un flash swap en los pools de Balancer elimina la necesidad de mantener cualquiera de los tokens de entrada que tradicionalmente se requieren para ejecutar un intercambio. En cambio, al identificar un desequilibrio, puedes instruir al Vault para que ejecute el intercambio y posteriormente reciba la recompensa.
0x1.1 Varios Pools en Balancer
A continuación, presentamos brevemente algunos conceptos de pools que son relevantes para esta vulnerabilidad.
-
Linear Pools: Los pools
Linear[2] son pools de Balancer que facilitan el intercambio de un activo y su contraparte envuelta y generadora de rendimiento a una tasa de cambio conocida. Como su nombre indica, los poolsLinearutilizan Matemáticas Lineales. Un poollinearcontiene tres tokens, incluyendo:- dos activos, es decir, tokens
mainywrappedque tienen un token subyacente de igual valor; - el
BPTcorrespondiente (Balancer Pool Token). Tenga en cuenta que losBPTson tokens ERC-20.
- dos activos, es decir, tokens
-
Nesting Linear Pools: El BPT de un Linear Pool puede anidarse dentro de otro pool. Esto crea una ruta simple de
batchSwapentre los activos base y los tokens en el pool externo, ya que los intercambiadores pueden intercambiar desdeBPTa uno de los tokens subyacentes del Linear Pool. -
Composable Stable Pools: Los Composable Stable Pools [3] están diseñados para activos que se espera que intercambien de manera consistente cerca de la paridad o a una tasa de cambio conocida. Los Composable Stable Pools utilizan Matemáticas Estables que permiten intercambios de tamaño significativo antes de encontrar un impacto sustancial en el precio, aumentando enormemente la eficiencia de capital para intercambios de tipo similar y correlacionado.
Un pool es componible cuando permite intercambios hacia y desde su propio token LP. Colocar su token LP en otros pools (o "anidar") permite un
batchSwapfácil desde tokens de pools anidados a tokens en el pool externo. -
Boosted Pools: Los pools
Boosted[4] están diseñados para mejorar la eficiencia de capital de la liquidez inactiva en pools grandes. Los poolsBoostedson en realidad una subclase de otros pools. Por ejemplo, un poolboostedpodría construirse sobre poolslinear.Los Boosted Pools están diseñados para ofrecer una alta eficiencia de capital al permitir a los usuarios proporcionar liquidez de intercambio para tokens comunes mientras reenvían los tokens inactivos a protocolos externos. Esto otorga a los proveedores de liquidez los beneficios de protocolos como Aave además de las comisiones de intercambio que recaudan.
0x1.2 Un Ejemplo Concreto de los Boosted Pools Vulnerables: Balancer Boosted Aave USD
Balancer Boosted Aave USD (símbolo: bb-a-USD) es un Composable Stable Pool que facilita intercambios entre tres stablecoins (es decir, USDC, USDT y DAI) mientras envía la liquidez inactiva a Aave. Los pools linear subyacentes son:
bb-a-USDC(compuesto por USDC y aUSDC envuelto)bb-a-USDT(compuesto por USDT y aUSDT envuelto)bb-a-DAI(compuesto por DAI y aDAI envuelto)
En concreto, bb-a-USD es una colección de un Composable Stable Pool que contiene los tokens de pool de tres pools linear diferentes, y cada uno de esos pools linear tiene un token estable asociado: DAI, USDC y USDT. La siguiente figura proporcionada por el documento oficial [5] muestra la estructura de bb-a-USD:

0x1.3 Cómo Calcular el Precio del BPT
Una pregunta importante que surge de manera natural es cómo determinar el precio del BPT al intercambiar una cantidad específica (es decir, amountIn) de BPT por una cierta cantidad (es decir, amountOut) de otro token.
Balancer proporciona una descripción detallada de las fórmulas matemáticas que adoptaron [6, 7] para diferentes pools. Por razones de simplicidad, aquí abstraemos y resumimos los conceptos más relevantes.
Tomando el pool linear como ejemplo, el precio del BPT se calcula en la función onSwap del contrato LinearPool.

El cálculo puede resumirse de la siguiente manera:

Aquí tokenRate se calcula con la siguiente fórmula:

es un valor constante: .
En la fórmula anterior, el numerador puede simplificarse como la suma del saldo del token main y el saldo del token wrapped, mientras que el denominador es la diferencia entre un valor predefinido (es decir, _INITIAL_BPT_SUPPLY) y el saldo del BPT.
Vale la pena señalar que los saldos de todos los tokens involucrados deben ser nominalizados antes de realizar el cálculo, porque diferentes tokens pueden tener diferentes decimales. En concreto, el saldo bruto de un token dado se multiplicará por un factor de escalado correspondiente, que está determinado por la función _scalingFactors.
(1) Factores de Escalado de los Pools Linear
Tanto el BPT como el token main tienen un factor de escalado regular y constante.

(2) Factores de Escalado de los Pools Boosted como bb-a-USD
El cálculo de un pool boosted es un poco más complicado. En concreto, el factor de escalado devuelto es el producto del factor de escalado bruto (p. ej., 1e18) y la tasa del token, que se obtiene de la tasa de token en caché si existe.

¿De dónde proviene la tasa de token en caché? Existe una función privada denominada _updateTokenRateCache. Obviamente, esta función primero recuperará la tasa invocando la función getRate de ese token y luego la almacenará en caché.

Nuevamente, tomando bb-a-USDC como ejemplo, la lógica central de la función getRate correspondiente sigue la fórmula que discutimos anteriormente.

Tenga en cuenta que existen tres posibles rutas que pueden activar la función _updateTokenRateCache:

Además, existe una verificación de expiración al realizar actualizaciones para las rutas que pasan por la función onSwap:

0x2 Análisis de la Vulnerabilidad
La causa raíz radica en la manipulación de precios causada por la lógica de redondeo hacia abajo dentro de la función onSwap del pool linear. Esto, a su vez, afecta de manera inapropiada a la tasa de token en caché utilizada por el pool boosted.
En concreto, el amountOut se redondea hacia abajo cuando se invoca la función _downscaleDown. Por lo tanto, si existe una diferencia de magnitud significativa entre amountOut y scalingFactors[indexOut], el valor de retorno de la función _downscaleDown podría ser cero.

Por ejemplo, si usamos bb-a-USDC (como BPT) para intercambiar USDC (como el token main) en el pool bb-a-USDC, cuando amountOut es menor que 1.000.000.000.000, el valor de retorno siempre se redondeará hacia abajo a cero. Esto aumentaría el saldo de bb-a-USDC ya que podría considerarse como agregar liquidez de bb-a-USDC de forma unidireccional.
Como resultado, si BPT es el token utilizado para el intercambio, su tasa aumentará en línea con la fórmula para calcular la tasa, dado que el numerador permanece igual mientras el denominador disminuye. Este error podría explotarse para generar una diferencia de precio (enorme).
0x3 Análisis del Ataque
La transacción de ataque consta de los siguientes pasos de ataque:
- Tomar prestados 300.000 USDC mediante Flashloan de Aave.
- Intercambiar 1,067753 USDC por 0,970495 aUSDC en el pool bb-a-USDC.
- Realizar
batchSwapen los poolsbb-a-USDCybb-a-USD, es decir, obtener 15.628bb-a-USDC, 139.431bb-a-DAIy 248.868bb-a-USDTcon 42.203USDC. Los pasos detallados se resumen en la siguiente tabla (con decimales):

- Intercambiar tokens LP por los tokens estables subyacentes correspondientes:
- 139.431
bb-a-DAI-> 141.127DAIen el poolbb-a-DAI - 15.628
bb-a-USDC-> 15.685USDCen el poolbb-a-USDC - 248.868
bb-a-USDT-> 253.461USDTen el poolbb-a-USDT
- Reembolsar el flashloan, y el beneficio final es:
- 114.324
DAI - 253.461
USDT - 0,970495
aUSDC
Vale la pena señalar que el atacante drenó aUSDC con USDC del pool bb-a-USDC en el paso 2, lo que haría que la manipulación de precios en el paso 3 fuera mucho más fácil, es decir, el atacante solo necesitaba centrarse en USDC y bb-a-USDC.
El paso 3 juega el papel clave. Ahora profundicemos en los detalles de este paso para comprender por qué el atacante podía obtener beneficios. En concreto,
- El paso 3.1 se usa para drenar
USDCconbb-a-USDCdel poolbb-a-USDC; - Los pasos 3.3 y 3.4 se usan para intercambiar
bb-a-USDCporbb-a-DAI, mientras que el paso 3.5 se usa para intercambiarbb-a-USDCporbb-a-USDT. - El paso 3.7 se usa para intercambiar
USDCporbb-a-USDCdel poolbb-a-USDC.
Aquí los pasos 3.2 y 3.6 no intercambian ningún token objetivo (es decir,
USDC) debido al redondeo hacia abajo discutido anteriormente, por lo tanto, los saldos de los tokens objetivo permanecen sin cambios después del intercambio, lo que puede considerarse como agregar liquidez extra debb-a-USDCal poolbb-a-USDC.
Obviamente, los intercambios anómalos ocurren principalmente en los pasos 3.4, 3.5 y 3.7. A continuación, repasaremos los detalles de cada uno de estos pasos.
(1) bb-a-USDC -> bb-a-DAI
En el paso 3.3, la tasa de cambio entre bb-a-USDC y bb-a-DAI es casi 1, mientras que en el paso 3.4, la tasa de cambio se convierte en 19:
- Paso 3.3: 1.000.339.378.515.783.699 / 1.000.000.000.000.000.000 = 1,00
- Paso 3.4: 139.430.482.942.020.211.267.110 / 7.300.000.000.000.000.000.000 = 19,10
Recordando la lógica del código que discutimos anteriormente, en el paso 3.3, después de devolver la tasa de token en caché previamente para calcular el factor de escalado (1.012.181.365.780.643.700), actualiza la tasa para calcular un nuevo valor (40.240.000.000.000.000.000). Este valor actualizado se usa luego en el paso 3.4 como el nuevo factor de escalado. Dado que los factores de escalado brutos permanecen sin cambios (es decir, 1e18), esto implica que la nueva tasa es aproximadamente 40 veces mayor que la tasa anterior.

Sin embargo, ¿de dónde proviene este aumento significativo? Revisemos la fórmula para calcular tokenRate. Dado que el saldo de aUSDC se ha agotado en el paso 2, el cálculo de tokenRate puede simplificarse de la siguiente manera:


Aquí el valor real de nominalMainBalance se debe al redondeo hacia abajo que ocurre en el paso 3.2.
(2) bb-a-USDC -> bb-a-USDT
El paso 3.5 usa el mismo truco para obtener más bb-a-USDT, y la tasa de cambio entre bb-a-USDC y bb-a-USDT es más de 12:
- 248.868.905.733.352.246.491.156 / 20.000.000.000.000.000.000.000 = 12,44
(3) USDC -> bb-a-USDC
Además, bptBalance aumenta en el paso 3.6, luego bptSupply se convierte en cero en el paso 3.7. De esta manera, es posible intercambiar USDC por bb-a-USDC a una tasa de cambio cercana a 1:1.

0x4 Resumen de Ataques y Beneficios
En el momento de escribir esto, hemos observado decenas de ataques en la práctica, causando pérdidas que superan los $2,12M. En resumen, estos ataques fueron ejecutados por tres cuentas distintas, de la siguiente manera:

Balancer sufrió una pérdida total de ~$1M debido a esta vulnerabilidad. Menos de 12 horas después del ataque inicial a Balancer, su protocolo bifurcado, Beethoven X, sucumbió a ataques similares, lo que llevó a una pérdida estimada de ~$1,1M. ¡Beethoven X incurrió en pérdidas aún mayores que Balancer! La pérdida acumulada de este incidente de seguridad ascendió a ~$2,12M.
Una lista completa de estas transacciones de ataque se ha recopilado en un documento que preparamos. Por favor, consúltelo para obtener información más detallada.
Algunas Observaciones Sobre los Atacantes
Al analizar las transacciones iniciadas por cada red, encontramos una divergencia significativa en el rastro de las transacciones de ataque en Fantom en comparación con las de Ethereum y Optimism.
En concreto, más allá de las diferencias notables en las funciones clave, el atacante en Fantom aprovechó dos trucos únicos para evitar ser adelantado por Bots MEV. Además, los fondos utilizados para el ataque en Fantom fueron preparados 163 días antes del ataque.
A partir de las observaciones detalladas anteriormente, podemos inferir:
-
Al menos dos atacantes distintos estuvieron involucrados.
-
El atacante en Fantom es un delincuente reincidente experimentado.
0x5 Conclusión
En resumen, esta es una vulnerabilidad sutil arraigada en la lógica de redondeo hacia abajo. Sin embargo, explotar esta vulnerabilidad no es sencillo. En concreto, el atacante pudo inflar la tasa de token en caché explotando el problema de redondeo hacia abajo en el pool linear, manipulando así el precio del token en el pool boosted correspondiente.
Este incidente también enfatiza la importancia de la notificación oportuna a aquellos proyectos que han bifurcado desde la fuente vulnerable. A pesar de la alerta de Balancer, los ataques dirigidos a protocolos bifurcados continúan, destacando la necesidad de que estos proyectos bifurcados se mantengan informados sobre las actualizaciones de seguridad de sus proyectos fuente. Sin embargo, garantizar que estos proyectos bifurcados reciban notificaciones oportunas representa un desafío continuo para la comunidad.
Además, la serie continua de ataques subraya la importancia de la prevención proactiva de amenazas, lo que podría ayudar eficazmente a mitigar pérdidas potenciales.
Referencia
- [1] https://docs-v2.balancer.fi/concepts/overview/basics.html
- [2] Linear Pools: https://docs-v2.balancer.fi/concepts/pools/linear.html
- [3] Composable Stable Pools
- [4] Boosted Pools
- [5] https://docs-v2.balancer.fi/concepts/pools/boosted.html#example
- [6] https://docs-v2.balancer.fi/reference/math/linear-math.html
- [7] https://docs-v2.balancer.fi/reference/math/stable-math.html



