Skip to content

Commit

Permalink
Merge pull request #1 from ape-dev-cs/master
Browse files Browse the repository at this point in the history
Introduce an evil token
  • Loading branch information
devanoneth authored Jan 8, 2022
2 parents e768455 + 1e6d67d commit c3172ec
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 0 deletions.
49 changes: 49 additions & 0 deletions contracts/test/EvilERC20.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

import "../lib/UniswapV2Library08.sol";
import "@rari-capital/solmate/src/tokens/ERC20.sol";
import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol";

// An ERC20 token which can only be bought but not sold
contract EvilERC20 is ERC20 {
address private immutable uniPair;
address private immutable haxor;

constructor(address router) ERC20("Evil", "EVIL", 18) {
IUniswapV2Router02 uniRouter = IUniswapV2Router02(router);
uniPair = UniswapV2Library08.pairFor(uniRouter.factory(), uniRouter.WETH(), address(this));

_mint(msg.sender, 1000e18);
haxor = msg.sender;
}

function transfer(address to, uint256 amount) public override returns (bool) {
if (to == uniPair && isContract(msg.sender)) {
// Sorry babe, this is a honey pot
return false;
}
return super.transfer(to, amount);
}

function transferFrom(
address from,
address to,
uint256 amount
) public override returns (bool) {
if (to == uniPair && isContract(from)) {
// Sorry babe, this is a honey pot
return false;
}

return super.transferFrom(from, to, amount);
}

function isContract(address _addr) private view returns (bool) {
uint32 size;
assembly {
size := extcodesize(_addr)
}
return (size > 0);
}
}
55 changes: 55 additions & 0 deletions contracts/test/TokenBuyer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

import "../lib/IERC20.sol";

import "../lib/UniswapV2Library08.sol";

import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol";

contract TokenBuyer {
function buyTokens(
address routerAddress,
address tokenAddress,
uint256 tolerance
) public payable {
IUniswapV2Router02 router = IUniswapV2Router02(routerAddress);

//Get tokenAmount estimate (can be skipped to save gas in a lot of cases)
address[] memory pathBuy = new address[](2);
uint256[] memory amounts;
pathBuy[0] = router.WETH();
pathBuy[1] = tokenAddress;
IERC20 token = IERC20(tokenAddress);

amounts = UniswapV2Library08.getAmountsOut(router.factory(), msg.value, pathBuy);
uint256 buyTokenAmount = amounts[amounts.length - 1];

//Buy tokens
uint256 scrapTokenBalance = token.balanceOf(address(this));
router.swapETHForExactTokens{value: msg.value}(buyTokenAmount, pathBuy, address(this), block.timestamp);
uint256 tokenAmountOut = token.balanceOf(address(this)) - scrapTokenBalance;
require(tokenAmountOut > 0, "Can't sell this.");
}

function sellTokens(
address routerAddress,
address tokenAddress
) public payable {
IUniswapV2Router02 router = IUniswapV2Router02(routerAddress);

address[] memory pathSell = new address[](2);
pathSell[0] = tokenAddress;
pathSell[1] = router.WETH();
IERC20 token = IERC20(tokenAddress);

token.approve(routerAddress, type(uint256).max);
router.swapExactTokensForETHSupportingFeeOnTransferTokens(
token.balanceOf(address(this)),
0,
pathSell,
address(this),
block.timestamp
);
}
}
4 changes: 4 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

51 changes: 51 additions & 0 deletions test/toleranceCheck.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
import { expect } from "chai";
import { parseEther } from "ethers/lib/utils";
import { ethers } from "hardhat";
import { IUniswapV2Router02 } from "../typechain";
const utils = ethers.utils;
Expand Down Expand Up @@ -83,4 +84,54 @@ describe("ToleranceCheck", async function () {
// reverts = fail
await expect(ethers.provider.call({ data: deployData, value: utils.parseEther("1") })).to.be.reverted;
});

it("EvilERC20 should be okay, when in fact when bought by a contract, it's evil", async function () {
// Deploy an evil ERC20 token
const EvilERC20 = await ethers.getContractFactory("EvilERC20");
const evilERC20 = await EvilERC20.deploy(router.address);
await evilERC20.deployed();

// List on Uniswap
await evilERC20.approve(router.address, utils.parseEther("1000"));
await router.addLiquidityETH(
evilERC20.address,
utils.parseEther("1000"),
utils.parseEther("1000"),
utils.parseEther("5"),
deployer.address,
deadline,
{ value: utils.parseEther("5") },
);

// Perform the tolerance check test
const ToleranceCheck = await ethers.getContractFactory("ToleranceCheck");
const deployData = ToleranceCheck.getDeployTransaction(
router.address,
evilERC20.address,
utils.parseEther("0.01"),
).data;
const returnedData = await ethers.provider.call({
data: deployData,
value: utils.parseEther("1"),
});

// 0x01 = true = successful
expect(returnedData).to.be.eq("0x01");

// TokenBuyer is a simplified sandwich bot - it attempts to sandwich traders who buy with high slippage
// In this scenario we've seen someone buying evilERC20 with a high amount of slippage, and our tolerance check contract says it's all good to sandwich!
const TokenBuyer = await ethers.getContractFactory("TokenBuyer");
const tokenBuyer = await TokenBuyer.deploy();
await tokenBuyer.deployed();

// we've bought tokens
await tokenBuyer.buyTokens(router.address, evilERC20.address, utils.parseEther("2"), { value: parseEther("2") });

// the sandwiched trade hopefully gets executed here

// oh dear, we've been salmonella'd
await expect(tokenBuyer.sellTokens(router.address, evilERC20.address)).to.be.revertedWith(
"TransferHelper: TRANSFER_FROM_FAILED",
);
});
});

0 comments on commit c3172ec

Please sign in to comment.