moon

moon

Build for builders on blockchain
github
twitter

Defi黑客系列:Damn Vulnerable DeFi (十) - Free rider

Damn Vulnerable DeFi 是一个 Defi 智能合约攻击挑战系列。内容包括了闪电贷攻击、借贷池、链上预言机等。在开始之前你需要具备 Solidity 以及 JavaScipt 相关的技能。针对每一题你需要做的就是保证该题的单元测试能够通过。

题目链接:https://www.damnvulnerabledefi.xyz/challenges/10.html

题目描述:

现有已经发布的 Damn Valuable NFT 交易市场,初始 mint 了 6 个 NFT,并且可以在交易市场上出售,售价为 15ETH。

有个买家告诉了你一个秘密:市场是脆弱的,所有的代币都可以被拿走。然而,他并不知道怎么做。为此愿意提供 45 ETH 的奖励给取出 NFT 并发送给他的人。

你想在这个买家那里建立一些名声,所以你已经同意了这个计划。

遗憾的是你只有 0.5 ETH。要是有一个地方你可以免费获得 ETH 就好了,暂时性的也可以。

FreeRiderBuyer.sol#

买家合约

由于该合约继承自 IERC721Receiver ,且 NFT 合约继承自 ERC721

意味着当 NFT 合约调用 safeTransferFromto 地址是该合约时,会触发 onERC721Received 函数。

safeTransferFrom 内部会判断 to 是否是合约地址,是的话则会调用 onERC721Received

FreeRiderNFTMarketplace.sol#

交易合约,继承自 ReentrancyGuard, 用于防止重入攻击。

构造函数中创建了一个 DamnValuableNFT 合约,并 mintamountToMint 数量的 NFT 给了合约部署者

该合约提供了两种功能

  • 批量出售 offerMany

NFT 持有人调用该方法,除了一些基本的校验之外,还会授权该合约可以对持有人持有的 NFT 进行操作。

最后将报价保存到 offers[tokenId],当有人购买时,由于持有人已经授权了该合约可以操作其所持有的 NFT,故在收到付款后,该合约可以直接将持有人的 NFT 转给购买人。

  • 批量购买 buyMany

当有卖家在交易合约中出售其持有的 NFT 后,买家就可以执行购买流程,首先获取价格,并确保收到的 ETH 不低于售价,之后由该合约将卖家持有的 NFT 转给买家并将收到的 ETH 转给卖家。

批量购买功能其实有个明显的问题,在检查收到的 ETH 要不少于售价时 require(msg.value >= priceToPay, "Amount paid is not enough"); 检查的是单个 NFT 的价格,而不是批量购买的总额。而且 ETH 转给卖方的时候用的是合约内的余额。

例如交易合约持有100ETH, 卖家出售其持有的 tokenId 为 1, 2, 3 的 NFT,价格分别为 1ETH, 5ETH, 3ETH。此时买家购买调用 buyMany 应付的价格为 9ETH,但实际上仅需要支付5ETH(购买价最高的 tokenId 的价格)就能满足 require 的检查。此时交易合约共持有105ETH 。转账时,交易合约共向卖方们转了9ETH。即买方购买时少付的 ETH 由交易合约的余额补上了。

在本题中,NFT 的单价为15ETH,因此只需要付15ETH即可购买 6 个NFT,而不是90ETH。但你只有0.5ETH。因此要想办法获得 ETH ,通过 uniswap 的闪电贷就是不错的方法:

  • 通过闪电贷借出15WETH,并将 WETH 换成 ETH
  • 使用 ETH 购买 NFT,并将购买的 NFT 发送给 FreeRiderBuyer,收到45ETH的奖励。
  • 最后将 ETH 换回 WETH,算上闪电贷的费率还清闪电贷的借款

由于这些操作需要再一笔交易中完成,因此需要写一个攻击合约,不过在此之前需要先看下测试文件 free-rider.challenge.js 做了哪些初始化:

  • attacker 发送了 0.5ETH
  • 部署合约 WETHDVTuniswapFactoryuniswapRouter
  • 通过调用 uniswapRouter 合约的方法 addLiquidityETH 添加流动性,该方法内部会创建配对合约 uniswapPair,配对的币种是WETH-DVT ,添加的流动性为 9000WETH15000DVT
  • 部署合约 FreeRiderNFTMarketplace,并转入90ETH
  • 部署 NFT 合约
  • 在交易合约中创建卖单出售 tokenId 为 0~5 的 NFT ,每个价格都为 15ETH
  • 部署合约 FreeRiderBuyer 并转入45ETH

攻击合约代码如下:

在测试文件 free-rider.challenge.js 添加执行入口

最后执行 yarn free-rider 测试通过!

完整代码

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。