paint-brush
Cómo perder $ 13 de los fondos de los usuarios (como desarrollador de Blockchain)por@msokola
579 lecturas
579 lecturas

Cómo perder $ 13 de los fondos de los usuarios (como desarrollador de Blockchain)

por Matéush7m2022/12/20
Read on Terminal Reader

Demasiado Largo; Para Leer

Los tipos de datos son importantes y descuidarlos conduce a graves consecuencias. Los lenguajes modernos como JavaScript y Python están utilizando "escribir pato" para determinar el tipo de variable. En la mayoría de los casos, su intérprete de idioma manejará los tipos de la manera correcta. El problema comienza cuando su programa necesita ejecutar ecuaciones (incluso simples) en números grandes.
featured image - Cómo perder $ 13 de los fondos de los usuarios (como desarrollador de Blockchain)
Matéush HackerNoon profile picture
0-item

El gobierno dice que no estamos en recesión, pero al mismo tiempo, escuchamos sobre inflación vertiginosa, aumentos en las tasas de interés y despidos en casi todos los sectores de la economía.


A pesar de que las criptomonedas y TradFi se vieron más afectadas, muchas empresas aún están construyendo sus tokens, protocolos y productos DeFi. ¿Eres uno de ellos?


Hoy hablaré sobre los tipos de datos y un momento. Tengo algo muy importante que decir. Podrías imaginarme como un profesor del MIT de más de 60 años que tortura a los estudiantes con conferencias sobre temas que ya no importan. Pero eso no es cierto.


Los tipos de datos siguen siendo importantes, y descuidarlos conduce a graves consecuencias. Intentaré repasar brevemente todos los problemas potenciales y abordarlos para que no encuentres los 8 minutos que dedicas a leer este artículo en vano.


Los lenguajes modernos como JavaScript y Python están utilizando "escribir pato" para determinar el tipo de variable. Si asignamos este tipo de fórmula a = 2 + 2 a una variable, el intérprete del lenguaje sabe que está tratando con números y realizará operaciones matemáticas en este literal.


La tipificación del pato se puede explicar con esta frase: “Si camina como un pato y grazna como un pato, entonces debe ser un pato”. Cuando miras más de cerca el significado de esto, tiene mucho sentido. Si literal tiene letras y números, debe ser una cadena, y eso está claro. Pero, ¿y si tiene números?


¿Es un boolean , integer , decimal , float o date ? En la mayoría de los casos, su intérprete de idioma manejará los tipos de la manera correcta. El problema comienza cuando su programa necesita ejecutar ecuaciones (incluso simples) en números grandes.


“Si camina como un pato y grazna como un pato, entonces debe ser un pato”, ¿no? En realidad no lo es.


Denominaciones de Ethereum en pocas palabras

En los siguientes párrafos, me refiero a las denominaciones comunes de Ethereum: wei y gwei . Permítanme presentarles brevemente para que hablemos el lenguaje común.


La denominación más pequeña es 1 wei , y 1 ether equivale a 1,000,000,000,000,000,000 Wei (18 ceros) . Repito - 18 ceros. Es difícil pensar en números tan grandes, pero marcan la diferencia e importan mucho.


La siguiente denominación común es 1 gwei . 1 éter equivale a 1 000 000 000 gwei (9 ceros) . Gwei es más llevadero para los humanos: al final, todos quieren ser millonarios, ¿no? (guiño guiño)


Resumámoslo: 1 éter es igual a:

  • 1,000,000,000 gwei (9 ceros)
  • 1,000,000,000,000,000,000 wei (18 ceros)


Nota técnica: Ethereum tiene dos capas: la capa de ejecución y la capa de consenso. La capa de ejecución utiliza wei para representar los valores de ether y la capa de consenso utiliza gwei. Si eres un desarrollador de blockchain, debes aprender a interactuar con ambos.

Ejemplo del mundo real: punta de Stakefish y MEV Pool

Soy ingeniero de software en stakefish . Soy responsable de construir nuestra paleta de productos DeFi, y uno de los más recientes es nuestro grupo de propinas y MEV para Ethereum.


A partir del 15 de septiembre de 2022, todos los validadores son elegibles para recibir sugerencias de transacciones y pueden participar en MEV para obtener recompensas adicionales. Las propinas de transacción y los MEV se obtienen cuando el validador propone un nuevo bloque.


Decidimos crear un contrato inteligente que recopile todas las recompensas en una bóveda común y permita al usuario reclamar su parte de ella. No estoy tratando de publicitar nuestro producto, pero necesito establecer el contexto de este artículo.


Si está más interesado en este producto, puede leer más aquí . No te estoy vendiendo nada más que mi experiencia.


Como mencioné, tenemos un contrato inteligente que recibe sugerencias de transacciones y recompensas MEV obtenidas por validadores. Significa que nuestro contrato inteligente tiene un saldo bastante grande. Es 963+ ether ahora ($ 1.1M), y tenemos 8671 validadores que contribuyen a ello.


La parte crítica responsable de la sincronización entre la ejecución de Ethereum y la capa de consenso es Oracle . Es un sistema muy importante que nos permite determinar qué validadores están aportando al pool.


El oráculo está escrito en Python, pero podría estar escrito en JavaScript; el problema permanece sin cambios y lo demostraré pronto.


¡Vamos a profundizar en el código!

Por qué importan los tipos de datos

El saldo del contrato inteligente es ahora de 963 135 554 442 603 402 422 wei (963 ether). Este número no solo es difícil de entender para los humanos sino también para las computadoras (intérpretes de idiomas para ser exactos). Verifiquemos JavaScript:


 const vault_balance = parseInt("963135554442603402422") console.log(vault_balance) // 963135554442603400000 (lost 2422 wei in total)


Solo lancé el saldo de string a int , y ya tengo 2422 wei cortos. No hicimos ninguna ecuación todavía.


El saldo del contrato inteligente es tan alto gracias a muchos validadores que contribuyen a él. Ahora, calculemos cuál es la participación promedio del validador en el saldo del contrato ahora:


 const vault_balance = parseInt("963135554442603402422") const validator_count = 8671 const avg_validator_contribution = vault_balance / validator_count // 111075487768723730 (lost 7 wei per validator)


La participación promedio es 0.111 éter. Pero esta cantidad no es correcta, en realidad nos faltan 7 wei. Son 60,697 wei en total (7 wei por 8671 validadores). Mostraré el número correcto más adelante.


Avanzando más en el agujero del conejo de las pérdidas: calculemos la cantidad total de recompensas por validador dado. Tenga en cuenta que el usuario necesitaba depositar 32 ether para iniciar un validador, por lo que lo descontaré del saldo del validador.


Y tomaré como ejemplo un validador aleatorio que contribuye al contrato inteligente que tiene un saldo de 32.779 ether.


 const vault_balance = parseInt("963135554442603402422") // (lost 2422 wei) const validator_count = 8671 const avg_validator_contribution = vault_balance / validator_count // (lost 7 wei) const initial_deposit = parseInt("32000000000000000000") const validator_balance = parseInt("32779333896000000000") const total_validator_rewards = validator_balance - initial_deposit + avg_validator_contribution // 890409383768723700 (lost 23 wei per validator)


Las recompensas totales obtenidas por este validador equivalen a 0,8904 ether, pero este valor tampoco es exacto. En este momento, contamos mal 199,443 wei en total (23 wei por 8671 validador). Como puedes ver, esta forma de calcular números no es sostenible.

¿Qué sale mal?

Hay dos problemas con el código anterior:


  • En JavaScript, el valor seguro máximo para números enteros es igual a 2^53 - 1 solamente. Significa que puede manejar hasta 9007199254740991 wei (0,009 éter)


  • Técnicamente, podríamos haber usado BigInt pero tendríamos problemas con la división. Terminaríamos con valores "flotantes". Los flotadores son la raíz de todos los males en las finanzas porque son aproximados. Significa que pierden precisión. Necesitamos usar decimales. (La principal diferencia entre decimal y float es que el decimal almacena el valor exacto y el float se aproxima).


Si alguna vez ha realizado alguna codificación relacionada con Ethereum en JavaScript, debe haber oído hablar de ethers.js . Esta biblioteca contiene todas las utilidades necesarias para interactuar con la cadena de bloques. Para solucionar el problema anterior, utilizaremos una de las herramientas llamada BigNumber que admite números extremadamente grandes y maneja los decimales de la manera correcta.


¡Vamos a hacerlo!


 const vault_balance = BigNumber.from("963135554442603402422") // no loss const validator_count = BigNumber.from(8671) const avg_validator_contribution = vault_balance.div(validator_count) // no loss // 111075487768723723 const initial_deposit = BigNumber.from("32000000000000000000") const validator_balance = BigNumber.from("32779333896000000000") const total_validator_rewards = validator_balance.sub(initial_deposit).add(avg_validator_contribution) // 890409383768723723


Como puede ver, ahora terminamos con el número exacto. ¿Cómo sé que este es realmente el número correcto? Repetiré el mismo ejercicio en Python para demostrar que tengo razón.

Probémoslo en Python

Python admite números enteros largos, por lo que los valores no se cortarán repentinamente como hemos visto en JavaScript. Desafortunadamente, todavía determina todos los números flotantes como float por defecto:


 vault_balance = int("963135554442603402422") # no loss validator_count = 8671 avg_validator_contribution = vault_balance / validator_count # 111075487768723728 (5 wei too much) initial_deposit = int("32000000000000000000") validator_balance = int("32779333896000000000") total_validator_rewards = validator_balance - initial_deposit + avg_validator_contribution # 890409383768723712 (lost 11 wei)


¿Se pregunta dónde exactamente perdió precisión? La división avg_validator_contribution en float en lugar de decimal . El fragmento correcto se vería así:


 vault_balance = Decimal("963135554442603402422") validator_count = Decimal(8671) avg_validator_contribution = vault_balance / validator_count # 111075487768723723 initial_deposit = Decimal("32000000000000000000") validator_balance = Decimal("32779333896000000000") total_validator_rewards = validator_balance - initial_deposit + avg_validator_contribution # 890409383768723723


Ahora, los valores devueltos por Python y JavaScript son exactos. ¡Compruebe usted mismo!


Este tipo de pérdidas son marginales y se pueden pasar por alto fácilmente. A menudo, nos enteramos de ellos cuando se acumulan con el tiempo y crecen en números significativos.


Situaciones como esa siempre dan quebraderos de cabeza, no solo a los desarrolladores sino también a otros departamentos como el financiero o el jurídico. ¡Siempre debe probar sus fórmulas y nunca usar buenos números redondos para hacerlo!


Significaría mucho para mí si me sigues en Twitter . Estoy centrando mi actividad en la ingeniería de software y blockchain. Estoy abriendo la mayor parte de mi trabajo, por lo que es posible que desee consultar mi GitHub .