LOADING...
LOADING...
LOADING...
当前位置: 玩币族首页 > 币圈百科 > 分析Akutar NFT 2个亿被永久锁死事件始末

分析Akutar NFT 2个亿被永久锁死事件始末

2022-04-24 区块律动BlockBeat 来源:区块链网络
原文标题:《 Akutar NFT 的 2 个亿因为写错 1 个单词被永久锁死了?! 》
原文作者:今天有更懂这个世界一点了吗
本文来自微信公众号:今天有更懂这个世界一点了吗


大家可以关注我的公众号《今天有更懂这个世界一点了吗》查阅往期文章,与我进行更深度的交流探讨,也可以关注我的推特:jason_chen998


上篇写 NBA 的文章写的太累了大伤元气,想休息一段时间再写的,结果 web3 的世界实在是太精彩,每天发生的大新闻太多,大周末的又被迫营业。



今天一个叫 Akutar 的 NFT 项目因为合约 bug,导致 11539 个 ETH,价值 3400 万美金 2 个亿人民币的钱永久取不出来被锁死了,2 个亿啊!


我们打开合约地址看看这 2 个亿来眼馋眼馋,想象一下 Akutar 团队望着这一串数字的抱头痛哭的心情。



首先介绍一下 Akutar,从官网的描述和他们 twitter 可以看出,这不是一个土狗项目,相反是一个很用心的高质量项目,不论是从精细的画风还是 roadmap 描述质量都很高。



它的发起人是一位知名的棒球运动员 Micha Johnson,起源于他无意间听到一位黑人小男孩与母亲的对话,小男孩问母亲宇航员能否是黑人,所以 MichaJohnson 决定发行一系列梦想成为宇航员的戴着头盔的黑人小男孩,一个还算美妙的故事。



那么看着这么暖心的故事背后的 NFT 这么就砸了呢?从某种程度上还是项目方对于赚钱的渴望大过于所谓的暖心公益,从而搬起石头砸了自己的脚,因为它使用了一种比较独特的荷兰拍方式。


传统的拍卖方式是设置一个低价,然后大家向上叫价,最终出价最高者可购买,这是英式拍卖,荷兰式拍卖则是先设置一个最高价,然后逐渐的降价,最终有人在某个价格点出手将其买下来,荷兰拍更考验人性,因为每个人都想等最低价,但是都怕别人先于自己购买。


Azuki 就是用的是荷兰拍,但是 Akutar 相比于 Azuki 的拍卖方式又做了改变,Azuki 的价格是动态下降的,从而买的越晚价格越低,买的越早可能就吃了亏价格会高,Akutar 则加了一个「退款」规则,该规则看起来好像对用户更友好但是我认为实际上是想割更多的钱。


这如下图所示,拍卖起始价格是 3.5ETH,每过 6 分钟降低 0.1ETH,最终最低价格购买的人将成为标准价格,其他高于该价格购买的用户将获得退款,比如最后最低出售价格是 1.5ETH,那所有高于 1.5ETH 出价的人均会获得差价的退款,这种实际上是想让用户放心大胆的去买,不要蹲守最低价,即使买高了也能退款。



所以 Akutar 会有一个巨大的资金池用于存储所有用户交的钱,这部分钱包括项目方自己应得的,也包括需要退给用户的,这里先科普一个知识,之前的文章中也提到过,智能合约的性质和你自己个人的钱包地址是一样的,都是一个区块链地址,可以接收、发送虚拟货币,当你在 mint 某个项目时,实际上是你先将钱打到项目合约地址,然后合约给你转一个 NFT,即所有 NFT 的一级市场发售,钱都是先到了合约地址,再由项目方去进行提款操作,将合约里面的钱提到自己的钱包中。


这次 2 个亿被锁死就是因为在提款这个步骤出了 bug,因为区块链智能合约不可篡改的特性使得出现了 bug 是没法修的,传统互联网如果有个 bug 导致钱取不出来,修复迭代就可以,但是在 web3 中意味着这辈子你只能与这 2 个亿隔空相望。


我们来看一下一些关键的代码都做了什么帮助大家理解原理,再分析到底是哪里出了问题。


我们先学习一下荷兰拍的原理,首先是获取当前价格,这里先获取了最新区块的时间 block.timestamp,然后用当前时间减去开始时间 startAt 并除以 6(因为每 6 分钟降价一次),从而获取应该降价几次 timeElapsed,再用降价次数乘以降价金额计算出降价的总数 discount,最终用起始价格 startingPrice 减去降价金额得到当前应该要支付的费用。在代码中刚才提到的这些涉及到金额的参数其实都不是预先写在合约中的,而是可以修改的变量,说明项目方给自己留了后门可以视情况随时修改金额从而更好的挥舞镰刀。



怎么获取价格清楚了,我们再看用户出价的过程都发生了什么,这部分代码太长了我就不都贴了,挑重要的讲。


先获取了上面提到的当前价格,然后乘以用户购买的数量 amount,得到应该支付的总价 totalPrice,再判断用户实际支付的价格 value 是否大于总价,如果大于说明钱给够了接着向下执行。



这里先定义了一个报价 bid 都包含了什么,分别是 bidder 报价者地址,price 具体报价,bidsPlaced 总共购买数量,和 finalProcess 退款状态,0 是退款,1 是已退款,2 是取消退款。



接下来到了第一个埋坑的地方: totalBids 表示当前所拍卖出去的 NFT 数量,默认是 0,每次有用户报价则加上用户要购买的数量 amount,记住这里,等会会用到。



然后埋了第二个坑:使用了一个叫 bidIndex 的参数用于存储产生报价的用户有多少人。记住这两个参数,totalBids 存储了总共卖出多少个 NFT,bidIndex 存储了总共有多少人买了 NFT。



再讲一下项目方为用户退款的过程,项目方要先点击一个叫 processRefunds 的按钮开启退款,这个按钮背后的逻辑是把所有出价的用户全部循环处理一次,循环的次数就是刚才说的存储出价人数的 bidIndex。处理的内容是先判断该用户 finalProcess 退款状态是否为 0,0 表示尚未退款,如果为 0 的话继续向下执行,将用户当时的报价减去最低成交价,再乘以购买数量,则等于要退给用户的差价 refund。


然后将该 finalProcess 用户退款从 0 设置为 1,表示已经完成退款,从而该用户不能再去退了。


参数 refundProgress 是记录完成退款人数的,每退完一个用户就会加 1,因为是按照出价人数 bidIndex 循环的,所以 refundProgress 和 bidIndex 是一致的,这里其实没有毛病,本来出价的人和退款的人就应该是一样的,但是!接着向下看!



项目方提款的逻辑是怎样的,又有什么漏洞导致其无法提款?


下图为项目方进行提款的函数,即当项目方点击 claimProjectFunds 按钮后可以将钱提到自己钱包里,这里有三层校验,第一层是先验证当前是否已经结束了拍卖,如果结束进入第二层校验退款人数是否大于报价人数,其实这里项目方是好意,因为要确保每个人都退完了钱,项目方再提款,但就是这一层校验出了问题,不知道你还记不记得 totalBids 是什么意思?是售出 NFT 数量呀,不是报价人数!



你会问那这又怎么了呢?一个人在报价的时候是可以购买多个 NFT 的呀,退款人数实际上是购买人数,你要求购买人数超过卖出 NFT 数量,但是每人又可以买多个,那只要有 1 个人买了 2 个,就意味着购买人数永远不可能大于卖出数量,10 个人卖出了 11 个,你怎么要求 10 大于 11 呢?


我们上 etherscan 看一下,refundProgess 的数量是 3699,说明共有 3699 人报价,但是 totalBids 的数量是 5495,即共卖出了 5495 个,远远超过 3699,这辈子 refundProgess 都不可能大于 totalBids,这 2 个亿就永远被锁死在了合约中供后人观摩。




所以是项目方写错单词了,本来应该是想写 bidIndex 购买人数,结果写成了 totalBids 卖出数量,一个单词价值 2 个亿,这应该是全世界最贵的一个单词了,大家给我狠狠的记住这个单词 totalBids,就是它值 2 个亿!


通过这篇文章带着大家学习了一种新的 mint 方式荷兰拍以及其原理,另外带大家认识了一个 2 个亿的单词 totalBids。


原文链接


—-

编译者/作者:区块律动BlockBeat

玩币族申明:玩币族作为开放的资讯翻译/分享平台,所提供的所有资讯仅代表作者个人观点,与玩币族平台立场无关,且不构成任何投资理财建议。文章版权归原作者所有。

知识 Web3 NFT
LOADING...
LOADING...