# JAY Reentry攻击分析

By [Fuzzingq](https://paragraph.com/@liusy) · 2023-11-25

---

2022年12月9日，ETH链上Jay合约遭受重入攻击，代币合约地址及代码：

[https://etherscan.io/address/0xf2919d1d80aff2940274014bef534f7791906ff2](https://etherscan.io/address/0xf2919d1d80aff2940274014bef534f7791906ff2)

问题的原因出在合约中的buyJayWithERC721()函数，该合约中调用了未检测的外部合约函数并且未检测balance状态的修改

先看一下jay代币的定价方式，池子中有一定数量的jay和eth。假设为totalsupplyJay和reserveEth。当用户需要用AmountEthIn的数量购买jay的时候，用户可以买到的jay数量为：

$$AmountJayOut = AmountEthIn/reserveEth \* totalsupplyJay$$

同样的。当用户打算卖出AmountJayIn数量的Jay并获取ETH是，他可以换到的ETH数量为：

$$AmountEthOut = AmountJayIn/totalsupplyJay\*reserveEth$$

接下来我们看下出问题的点，也就是函数buyJayWithERC721

    function buyJayWithERC721(
            address[] calldata _tokenAddress,
            uint256[] calldata ids
        ) internal {
            for (uint256 id = 0; id < ids.length; id++) {
                IERC721(_tokenAddress[id]).transferFrom(
                    msg.sender,
                    address(this),
                    ids[id]
                );
            }
        }
    

IERC721(\_tokenAddress\[id\]).transferFrom(msg.sender,address(this),ids\[id\]）这里的\_tokenAddress\[id\]是用户可控的参数，触发点位于：buyJay函数：

    // Sell NFTs (Buy Jay)
        function buyJay(
            address[] calldata erc721TokenAddress,
            uint256[] calldata erc721Ids,
            address[] calldata erc1155TokenAddress,
            uint256[] calldata erc1155Ids,
            uint256[] calldata erc1155Amounts
        ) public payable {
            require(start, "Not started!");
            uint256 total = erc721TokenAddress.length;
            if (total != 0) buyJayWithERC721(erc721TokenAddress, erc721Ids);
    
            if (erc1155TokenAddress.length != 0)
                total = total.add(
                    buyJayWithERC1155(
                        erc1155TokenAddress,
                        erc1155Ids,
                        erc1155Amounts
                    )
                );
    
            if (total >= 100)
                require(
                    msg.value >= (total).mul(sellNftFeeEth).div(2),
                    "You need to pay ETH more"
                );
            else
                require(
                    msg.value >= (total).mul(sellNftFeeEth),
                    "You need to pay ETH more"
                );
    
            _mint(msg.sender, ETHtoJAY(msg.value).mul(97).div(100));
    
            (bool success, ) = dev.call{value: msg.value.div(34)}("");
            require(success, "ETH Transfer failed.");
    
            nftsSold += total;
    
            emit Price(block.timestamp, JAYtoETH(1 * 10**18));
        }
    

所以目前攻击者可以做的事是：

调用buyJay合约，自定义参数address\[\] calldata erc721TokenAddress为任意的一个ERC721合约。

接下来看回公式：

$$AmountEthOut = AmountJayIn/totalsupplyJay\*reserveEth$$

如果用户想要获得更多的AmountEthOut,需要做的就是

1.  增加AmountJayIn
    
2.  减少totalsupplyJay
    
3.  增加reserveEth
    

而增加reserveEth是实现起来最简单的方案：只需要在call的时候代入eth就可以了。配合重入漏洞我们就可以做到：

    Jay.buyJay(erc721contract){value:xxxxETH}  --> buyJayWithERC721(erc721TokenAddress, erc721Ids)  --> IERC721(_tokenAddress[id]).transferFrom() --> erc721TokenAddress.transferFrom() --> 
    Jay.sell()
    

  
在调用sell的时候 由于Jay.buyJay(erc721contract){value:xxxxETH}传入了ETH，这时也就增加了adress(Jay).balance (reserveETH)。获取了更多的ETH。之后再卖掉xxxxETH兑换来的Jay即可。

以下是真实的攻击交易详情：用户通过闪电贷借出了72.5个ETH。先通过22ETH买入了13584899853779845952188个Jay。然后再次调用buyJay(){50.5ETH}的通过重入直接调用sell函数卖出13584899853779845952188 ，操纵reserveETH达到太高Jay价格的效果。

[https://explorer.phalcon.xyz/tx/eth/0xd4fafa1261f6e4f9c8543228a67caf9d02811e4ad3058a2714323964a8db61f6?line=11](https://explorer.phalcon.xyz/tx/eth/0xd4fafa1261f6e4f9c8543228a67caf9d02811e4ad3058a2714323964a8db61f6?line=11)

![](https://storage.googleapis.com/papyrus_images/bd675cd491706ab2fad0b1d8278082a5852204f17f734cfe43d78d8e9b848450.png)

---

*Originally published on [Fuzzingq](https://paragraph.com/@liusy/jay-reentry)*
