paint-brush
Como perder $ 13 dos fundos dos usuários (como desenvolvedor Blockchain)por@msokola
579 leituras
579 leituras

Como perder $ 13 dos fundos dos usuários (como desenvolvedor Blockchain)

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

Muito longo; Para ler

Os tipos de dados são importantes e negligenciá-los leva a sérias consequências. Linguagens modernas como JavaScript e Python estão usando “digitação de pato” para determinar o tipo de variável. Na maioria dos casos, seu interpretador de idiomas tratará os tipos da maneira correta. O problema começa quando seu programa precisa executar equações (mesmo simples) em números grandes.
featured image - Como perder $ 13 dos fundos dos usuários (como desenvolvedor Blockchain)
Matéush HackerNoon profile picture
0-item

O governo diz que não estamos em recessão, mas, ao mesmo tempo, ouvimos falar de inflação vertiginosa, aumento das taxas de juros e demissões em quase todos os setores da economia.


Apesar da criptografia e do TradFi serem os mais afetados, muitas empresas ainda estão construindo seus tokens, protocolos e produtos DeFi. És um deles?


Hoje, falarei sobre tipos de dados e aguarde. Tenho algo muito importante a dizer. Você pode me imaginar como um professor de mais de 60 anos do MIT torturando alunos com palestras sobre assuntos que não importam mais. Mas isso não é verdade.


Os tipos de dados ainda são importantes e negligenciá-los leva a sérias consequências. Vou tentar passar brevemente por todos os possíveis problemas e abordá-los para que você não ache os 8 minutos que gasta lendo este artigo desperdiçados.


Linguagens modernas como JavaScript e Python estão usando “digitação de pato” para determinar o tipo de variável. Se atribuirmos esse tipo de fórmula a = 2 + 2 a uma variável, o interpretador da linguagem saberá que está lidando com números e executará operações matemáticas sobre esse literal.


A tipificação do pato pode ser explicada por esta frase: “Se anda como um pato e grasna como um pato, então deve ser um pato”. Quando você olha mais de perto o significado disso - faz todo o sentido. Se literal tiver letras e números, deve ser uma string, e isso é claro. Mas e se tiver números?


É um boolean , integer , decimal , float ou date . Na maioria dos casos, seu intérprete de linguagem lidará com os tipos da maneira correta. O problema começa quando seu programa precisa executar equações (mesmo simples) em números grandes.


“Se anda como um pato e grasna como um pato, então deve ser um pato”, certo? Na verdade não é.


Denominações Ethereum em poucas palavras

Nos próximos parágrafos, estou me referindo às denominações comuns do Ethereum - wei e gwei . Deixe-me apresentá-los brevemente para que possamos falar a linguagem comum.


A menor denominação é 1 wei , e 1 éter equivale a 1.000.000.000.000.000.000 Wei (18 zeros) . Repito - 18 zeros. É difícil entender números tão grandes, mas eles fazem a diferença e importam muito.


A próxima denominação comum é 1 gwei . 1 éter é igual a 1.000.000.000 gwei (9 zeros) . Gwei é mais suportável para os humanos - no final, todo mundo quer ser milionário, certo? (pisca, pisca)


Vamos resumir - 1 éter é igual a:

  • 1.000.000.000 gwei (9 zeros)
  • 1.000.000.000.000.000.000 wei (18 zeros)


Nota técnica: Ethereum tem duas camadas - a camada de execução e a camada de consenso. A camada de execução está usando wei para representar valores de éter, e a camada de consenso está usando gwei. Se você é um desenvolvedor de blockchain, precisa aprender a interagir com ambos.

Exemplo do mundo real: ponta do Stakefish e MEV Pool

Sou engenheiro de software na stakefish . Sou responsável por construir nossa paleta de produtos DeFi, e um dos mais recentes é nosso pool de dicas e MEV para Ethereum.


A partir de 15 de setembro de 2022, todos os validadores são elegíveis para dicas de transação e podem participar de MEVs para ganhar recompensas adicionais. Dicas de transação e MEVs são ganhos quando o validador propõe um novo bloco.


Decidimos criar um contrato inteligente que coleta todas as recompensas em um cofre comum e permite que o usuário reivindique sua parte dele. Não estou tentando anunciar nosso produto, mas preciso definir o contexto deste artigo.


Se você está mais interessado neste produto, pode ler mais aqui . Não estou vendendo nada além da minha experiência.


Como mencionei, temos um contrato inteligente que recebe dicas de transação e recompensas MEV ganhas pelos validadores. Isso significa que nosso contrato inteligente tem um saldo bastante grande. É 963+ ether agora (US$ 1,1 milhão) e temos 8671 validadores contribuindo para isso.


A parte crítica responsável pela sincronização entre a execução do Ethereum e a camada de consenso é o Oracle . É um sistema muito importante que nos permite determinar quais validadores estão contribuindo para o pool.


O oráculo é escrito em Python, mas poderia ser escrito em JavaScript - o problema permanece inalterado, e irei prová-lo em breve.


Vamos nos aprofundar no código!

Por que os tipos de dados são importantes

O saldo do contrato inteligente é igual a 963.135.554.442.603.402.422 wei (963 ether) agora. Esse número não é apenas difícil de entender para humanos, mas também para computadores (intérpretes de linguagem para ser exato). Vamos verificar o JavaScript:


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


Eu apenas converti o saldo de string para int , e já estou com 2422 wei short. Ainda não executamos nenhuma equação.


O saldo do contrato inteligente é tão alto graças à contribuição de muitos validadores. Agora, vamos calcular qual é a participação média do validador no saldo do contrato agora:


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


A participação média é de 0,111 éter. Mas esse valor não está correto - na verdade, temos 7 wei a menos. São 60,697 wei no total (7 wei vezes 8.671 validadores). Vou mostrar o número correto mais tarde.


Continuando na toca do coelho das perdas - vamos calcular a quantidade total de recompensas por determinado validador. Lembre-se de que o usuário precisava depositar 32 ether para iniciar um validador, portanto, deduzirei do saldo do validador.


E vou tomar como exemplo um validador aleatório que contribui para o contrato inteligente que tem um 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)


O total de recompensas recebidas por este validador é igual a 0,8904 ether, mas esse valor também não é preciso. Neste momento, contamos incorretamente 199,443 wei no total (23 wei vezes 8671 validador). Como você pode ver, essa forma de calcular números não é sustentável.

O que dá errado?

Existem dois problemas com o código acima:


  • Em JavaScript, o valor seguro máximo para números inteiros é igual a 2^53 - 1 apenas. Isso significa que pode lidar com até 9007199254740991 wei (0,009 éter)


  • Tecnicamente, poderíamos ter usado o BigInt , mas teríamos problemas com a divisão. Terminaríamos com valores “flutuantes”. As flutuações são a raiz de todos os males nas finanças porque são aproximadas. Isso significa que eles perdem a precisão. Precisamos usar decimais. (A principal diferença entre decimal e float é que decimal armazena o valor exato e float aproxima).


Se você já fez alguma codificação relacionada ao Ethereum em JavaScript, deve ter ouvido falar sobre ethers.js . Esta biblioteca contém todos os utilitários necessários para interagir com o blockchain. Para corrigir o problema acima, usaremos uma das ferramentas chamada BigNumber que suporta números extremamente grandes e lida com decimais da maneira correta.


Vamos fazê-lo!


 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 você pode ver, agora acabamos com o número exato. Como sei que este é realmente o número certo? Vou repetir o mesmo exercício em Python para provar que estou certo.

Vamos tentar em Python

O Python suporta inteiros longos, portanto, os valores não serão cortados repentinamente, como vimos no JavaScript. Infelizmente, ele ainda determina todos os números flutuantes como float s por padrão:


 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)


Quer saber onde exatamente ele perdeu a precisão? A divisão avg_validator_contribution para float em vez de decimal . O trecho correto ficaria assim:


 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


Agora, os valores retornados por Python e JavaScript são exatos. Verifique você mesmo!


Esses tipos de perdas são marginais e podem ser facilmente ignorados. Freqüentemente, descobrimos sobre eles quando se acumulam ao longo do tempo e crescem para números significativos.


Situações como essa sempre dão dor de cabeça, não só aos desenvolvedores, mas também a outros departamentos, como financeiro ou jurídico. Você deve sempre testar suas fórmulas e nunca usar números redondos legais para fazer isso!


Significaria o mundo para mim se você me seguisse no Twitter . Estou focando minha atividade em engenharia de software e blockchain. Estou abrindo o código da maior parte do meu trabalho, então você pode querer verificar meu GitHub .