paint-brush
如何损失 13 美元的用户资金(作为区块链开发者)经过@msokola
579 讀數
579 讀數

如何损失 13 美元的用户资金(作为区块链开发者)

经过 Matéush7m2022/12/20
Read on Terminal Reader

太長; 讀書

数据类型很重要,忽视它们会导致严重的后果。 JavaScript 和 Python 等现代语言正在使用“鸭子类型”来确定变量的类型。在大多数情况下,您的语言解释器将以正确的方式处理类型。当您的程序需要对大数运行(甚至是简单的)方程式时,问题就出现了。
featured image - 如何损失 13 美元的用户资金(作为区块链开发者)
Matéush HackerNoon profile picture
0-item

政府说我们没有陷入衰退,但与此同时,我们听说几乎每个经济部门都在飞涨的通货膨胀、利率上升和裁员。


尽管加密货币和 TradFi 受到的影响最为严重,但许多公司仍在构建其代币、协议和 DeFi 产品。你是其中之一吗?


今天,我将谈论数据类型,等等。我有非常重要的话要说。你可能会把我想象成麻省理工学院一位 60 多岁的教授,用关于不再重要的科目的讲座来折磨学生。但事实并非如此。


数据类型仍然很重要,忽视它们会导致严重的后果。我将尝试简要介绍所有潜在问题并解决它们,这样您就不会发现您花在阅读本文上的 8 分钟是浪费了。


JavaScript 和 Python 等现代语言正在使用“鸭子类型”来确定变量的类型。如果我们将这种公式a = 2 + 2分配给一个变量,语言解释器就会知道它正在处理数字,并且会对这个文字执行数学运算。


鸭子类型可以用这句话来解释:“如果它走路像鸭子,叫起来像鸭子,那么它一定是鸭子”。当您仔细研究它的含义时 - 它非常有道理。如果 literal 有字母和数字,那肯定是字符串,这就明白了。但如果它有数字呢?


它是booleanintegerdecimalfloat还是date 。在大多数情况下,您的语言解释器将以正确的方式处理类型。当您的程序需要对大数运行(甚至是简单的)方程式时,问题就出现了。


“如果它走路像鸭子,叫起来像鸭子,那么它一定是鸭子”,对吧?其实不然。


以太坊面额简而言之

在接下来的段落中,我指的是以太坊的通用名称 - weigwei 。让我向您简要介绍一下他们,以便我们讲共同语言。


最小面额是1 wei1 以太等于1,000,000,000,000,000,000 Wei (18 个零) 我重复一遍——18 个零。很难将我们的思想集中在如此庞大的数字上,但它们会产生影响并且非常重要。


下一个通用面额是1 gwei1 以太等于1,000,000,000 gwei (9 个零) Gwei 更适合人类——说到底,每个人都想成为百万富翁,对吧? (眨眼眨眼)


让我们总结一下——1 以太等于:

  • 1,000,000,000 gwei(9 个零)
  • 1,000,000,000,000,000,000 wei(18 个零)


技术说明:以太坊有两层——执行层和共识层。执行层使用 wei 来表示以太值,共识层使用 gwei。如果您是区块链开发人员,则需要学习与两者交互。

真实世界的例子:Stakefish 的小费和 MEV 池

我是stakefish的一名软件工程师。我负责构建我们的 DeFi 产品选项板,最近的选项之一是我们的以太坊小费和 MEV 池。


从 2022 年 9 月 15 日开始,所有验证者都有资格获得交易提示,并可以参与 MEV 以获得额外奖励。交易提示和 MEV 是在验证者提出新区块时获得的。


我们决定构建一个智能合约,将所有奖励收集到一个公共金库中,并允许用户从中领取他们的份额。我不是要为我们的产品做广告,但我需要设置本文的上下文。


如果您对此产品更感兴趣,可以在此处阅读更多内容。除了我的经验,我不会卖给你任何东西。


正如我提到的,我们有一个智能合约可以接收交易提示和验证者获得的 MEV 奖励。这意味着我们的智能合约有相当大的余额。现在有 963+ 个以太币(110 万美元),我们有 8671 个验证者参与其中。


负责以太坊执行和共识层之间同步的关键部分是Oracle 。这是一个非常重要的系统,它使我们能够确定哪些验证者正在为矿池做出贡献。


oracle 是用 Python 写的,但它可以用 JavaScript 写——问题不变,我很快就会证明它。


让我们深入研究代码!

为什么数据类型很重要

智能合约的余额现在等于 963,135,554,442,603,402,422 wei(963 以太币)。这个数字不仅人类难以理解,而且计算机(准确地说是语言解释器)也难以理解。让我们检查一下 JavaScript:


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


我只是将余额从string转换为int ,我已经少了2422 wei 。我们还没有运行任何方程式。


由于许多验证者的贡献,智能合约余额如此之高。现在,让我们计算一下合约余额中验证者的平均份额:


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


平均份额为 0.111 以太币。但这个数额不正确——我们实际上少了 7 wei。总共是60,697 wei (7 wei 乘以 8671 个验证者)。我稍后会显示正确的数字。


进一步深入损失的兔子洞 - 让我们计算每个给定验证器的奖励总量。请记住,用户需要存入 32 个以太币才能启动验证器,因此我将从验证器余额中扣除。


我将以一个为智能合约做出贡献的随机验证者为例,该验证者的余额为 32.779 以太币。


 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)


该验证者获得的总奖励等于 0.8904 以太币,但这个值也不准确。此时,我们总共199,443 wei (23 wei 乘以 8671 验证者)。如您所见,这种计算数字的方法是不可持续的。

出了什么问题?

上面的代码有两个问题:


  • 在 JavaScript 中,整数的最大安全值仅等于2^53 - 1 。这意味着它最多可以处理9007199254740991 wei (0.009 以太币)


  • 从技术上讲,我们可以使用BigInt ,但我们会遇到除法问题。我们最终会得到“浮动”值。浮点数是金融界万恶之源,因为它们是近似值。这意味着他们失去了精度。我们需要使用小数。 (decimal 和 float 之间的主要区别在于 decimal 存储精确值而 float 近似值。)


如果您曾经用 JavaScript 编写过任何与以太坊相关的代码,那么您一定听说过ethers.js 。该库包含与区块链交互所需的所有实用程序。为了解决上述问题,我们将使用一种名为BigNumber的工具,它支持极大的数字并以正确的方式处理小数。


我们开始做吧!


 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


如您所见,现在我们得到了准确的数字。我怎么知道这实际上是正确的数字?我将在 Python 中重复同样的练习来证明我是对的。

让我们在 Python 中尝试一下

Python 支持长整数,因此值不会像我们在 JavaScript 中看到的那样突然被截断。不幸的是,它仍然默认将所有浮点数确定为float


 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)


想知道它到底在哪里失去了精度?该部门将avg_validator_contributionfloat而不是decimal 。正确的片段应该是这样的:


 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


现在,Python 和 JavaScript 返回的值是准确的。自己检查!


这些损失是微不足道的,很容易被遗漏。通常,当它们随着时间的推移复合并增长到可观的数量时,我们就会发现它们。


像这样的情况总是让开发人员头疼,也让财务或法律等其他部门头疼。您应该始终测试您的公式,并且永远不要使用漂亮的整数来这样做!


如果您在 Twitter 上关注我,这对我来说意义非凡。我的活动重点是软件工程和区块链。我正在开源我的大部分工作,所以你可能想查看我的 GitHub