# 复盘aishiba发币漏洞——项目方的低级bug，科学家的又一次狂欢

By [berwinYes](https://paragraph.com/@coolberwin) · 2023-04-24

---

AIshiba作为arb上一个比较早期的土狗NFT，毫不夸张的说引燃了arb上的NFT潮流。floor price曾一度最高达到0.04e，但是现在已经跌倒了0.004e（大概8刀）。

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

这就是妥妥的土狗项目，[官网](https://aishibaog.xyz/)做的也是极其简陋，连白皮书都没有，只有路线图。但是没办法，赶上了风口，猪都能飞上天，赶上arb的aidog热度。

并且在今天凌晨进行nft空投发币，空投规则是按照快照时的地址NFT个数计算，即NFT拥有数量越多，空投claim的代币越多，nft空投上限10个，超过按照10个计算。

谁知道发币出现bug，导致科学家发现漏洞，单号直接mint最大量的token **17700000000000 \* 10 \*\* 6** 。最高点卖出大概1e+。 [代币k线](https://www.dextools.io/app/cn/arbitrum/pair-explorer/0xbb1554b79d49327f6f000fb8057a972bfee4afca)

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

**核心原因是项目方利用**`Merkle Tree`**发放 空投token，但是本应该在后台的保存了全量地址被项目方放在了前端，并且未做任何混淆代码的保护处理。导致proof被轻易计算得到。(了解原理可参阅文末参考文献)**

只需要在官网按 `F12` 整个代码一览无余：

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

    merkleProof.js
    
    const { WHITELIST_ADDRESS } = require("./whitelist_Address")
    const keccak256 = require("keccak256")
    const { MerkleTree } = require("merkletreejs")
    
    
    const addressLeaves = WHITELIST_ADDRESS.map(x => keccak256(x))
    const merkleTree = new MerkleTree(addressLeaves, keccak256, {
        sortPairs : true
    })
    
    const rootHash = merkleTree.getHexRoot()
    
    module.exports = { merkleTree, rootHash }
    

并且调用合约的方法，传入参数也非常清晰：

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

    index.js
     
    const handleClaimForNfts = async () => {
        try {
          const signer = await getProviderOrSigner(true);
          const userAddress = signer.getAddress();
          const aiShibaContract = new Contract(
            AISHIBA_CONTRACT_ADDRESS,
            AISHIBA_CONTRACT_ABI,
            signer
          );
     
        
          const txn = await aiShibaContract.claimTokensForNft(merkleProof, nftBalance);
          await txn.wait();
          
    
          console.log("txn", txn);
          console.log("txn", "successful");
    
        } catch (error) {
          console.error(error);
        }
      };
    

可以看到调用了合约的 `claimTokensForNft()` 方法，传入了`merkleProof` 和 `nftBalance` 两个变量，nftBalance 代表着快照拥有NFT的数量。

其中`merkleProof` 只需要 用下列方法 `merkleTree.getHexProof(_leaf)` 即可获取

    index.js
    
    const getProof = async () => {
        const signer = await getProviderOrSigner(true);
        const _userAddress = await signer.getAddress();
        const _leaf = keccak256(_userAddress);
        const _merkleProof = merkleTree.getHexProof(_leaf);
        setUserAddress(_userAddress);
        setMerkleProof(_merkleProof);
      };
    

再看一下领取的[合约代码](https://arbiscan.io/address/0x3d3cceddb8f450ce107822c5f98f5e83200eb308#code)

        function claimTokensForNft(bytes32[] calldata proof, uint256 _amount) public claimIsLive {
            bytes32 leaf = keccak256(abi.encodePacked(msg.sender));
            require(MerkleProof.verify(proof, merkleRootNFT, leaf), "Wallet not eligible to claim");
    
            require(_amount > 0, "You do not own any tokens");
            require(!hasClaimedNFT[msg.sender], "You have claimed your Tokens");
    
            uint256 amountOfTokensToClaim;
            if(_amount<= 2) {
                amountOfTokensToClaim = tier1Claim;
            } else if( _amount > 2 && _amount <= 4){
                amountOfTokensToClaim = tier2Claim;
            } else if(_amount > 4 && _amount <= 9) {
                amountOfTokensToClaim = tier3Claim;
            } else if(_amount >= 10) {
                amountOfTokensToClaim = tier4Claim;
            }
    
            totalTokensClaimed += amountOfTokensToClaim;
            hasClaimedNFT[msg.sender] = true;
    
            require(tokenContract.transfer(msg.sender, amountOfTokensToClaim), "Transfer failed");
            emit HasClaimedNFT(msg.sender, amountOfTokensToClaim);
        }
    

`_amount` 即为nft的数量，合约按照 `_amount` 数量进行空投token。

*   `_amount<=2` 空投代币 1475000000000 \* 10 \*\* 6
    
*   `2<_amount<=4` 空投代币 3375000000000 \* 10 \*\* 6
    
*   `4<_amount<=9` 空投代币 7750000000000 \* 10 \*\* 6
    
*   `_amount>=10` 空投代币 17700000000000 \* 10 \*\* 6
    

**合约中没有对\_amount的校验操作，即使你的钱包地址拥有1个nft(即拥有白名单权限)，只要**`Merkle tree` **验证成功，\_amount传入10或者更大值也能**`mint` **到单地址最高数量代币，即 17700000000000 \* 10 \*\* 6 。**

大概有1800个地址领取了空投，绝大部分都是科学家

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

项目方发现了不对劲，在凌晨的00:45已经关闭了合约，并在接下来转走了合约中所有token。

据传言(道听途说)：背后的项目方是大学生，整个寝室一起出动一起赚钱，如果真是这样，现在这大学生还真他娘的敢想敢干，割起韭菜来也是毫不手软。

可惜 `螳螂捕蝉，黄雀在后`，项目成了科学家的提款机，最可怜的还是那些真金白银在DEX和CEX中买Token的韭菜们。

不玩土狗的我 看着大佬们的卖币截图和聊天记录，又一次感慨道：

**别人赚钱如呼吸般简单，自己赚钱如吃屎般困难。**

本人推特：[@coolberwin\_eth](https://twitter.com/coolberwin_eth)

文章参考：

Merkle tree的 原理： [WTF Ethers极简入门: MerkleTree脚本](https://github.com/WTFAcademy/WTF-Ethers/tree/main/17_MerkleTree)

---

*Originally published on [berwinYes](https://paragraph.com/@coolberwin/aishiba-bug)*
