| title |
Seaman |
| description |
Forcing contracts to trade at manipulated token prices |
| type |
Exploit |
| network |
|
| date |
2022-11-29 |
| loss_usd |
7000 |
| returned_usd |
0 |
| tags |
business logic |
price manipulation |
|
| subcategory |
|
| vulnerable_contracts |
0x6bc9b4976ba6f8c9574326375204ee469993d038 |
|
| tokens_lost |
|
| attacker_addresses |
0x4b1f47be1f678076f447585beba025e3a046a9fa |
0x0E647d34c4caF61D9E377a059A01b5C85AB1d82a |
|
| malicious_token |
|
| attack_block |
|
| reproduction_command |
forge test --match-contract Exploit_Seaman -vvv |
| attack_txs |
0x6f1af27d08b10caa7e96ec3d580bf39e29fd5ece00abda7d8955715403bf34a8 |
|
| sources |
|
- Flashloan some USDC
- Use the flashloan to buy all GVC in a pool
- Call
transfer() so contract buys GVC at current high price
- Sell your GVC
- Return flashloan
This is very similar to the attack on MBC. It actually involves the same method, swapAndLiquifyV1.
Every time someone called _transfer on Seaman, the swapAndLiquify calls where made. These methods would exchange the accumulated fee on the contract for GVC, passing through the BUSD pool.
function _transfer(
address from,
address to,
uint256 amount
) internal override {
// ...
if( uniswapV2Pair.totalSupply() > 0 && balanceOf(address(this)) > balanceOf(address(uniswapV2Pair)).div(10000) && to == address(uniswapV2Pair)){
if (
!swapping &&
_tokenOwner != from &&
_tokenOwner != to &&
!ammPairs[from] &&
!(from == address(uniswapV2Router) && !ammPairs[to])&&
swapAndLiquifyEnabled
) {
swapping = true;
swapAndLiquifyV3();
swapAndLiquifyV1();
swapping = false;
}
}
// ...
}
function swapAndLiquifyV1() public {
uint256 canlpAmount = lpAmount.sub(lpTokenAmount);
uint256 amountT = balanceOf(address(uniswapV2Pair)).div(10000);
if(balanceOf(address(this)) >= canlpAmount && canlpAmount >= amountT){
if(canlpAmount >= amountT.mul(5))
canlpAmount = amountT.mul(5);
lpTokenAmount = lpTokenAmount.add(canlpAmount);
uint256 beflpBal = lpToken.balanceOf(address(this));
swapTokensFor(canlpAmount,address(lpToken),address(this));
uint256 newlpBal = lpToken.balanceOf(address(this)).sub(beflpBal);
lpDivTokenAmount = lpDivTokenAmount.add(newlpBal);
isLpProc = true;
}
}
function swapAndLiquifyV3() public {
uint256 canhAmount = hAmount.sub(hTokenAmount);
uint256 amountT = balanceOf(address(uniswapV2Pair)).div(10000);
if(balanceOf(address(this)) >= canhAmount && canhAmount >= amountT){
if(canhAmount >= amountT.mul(5))
canhAmount = amountT.mul(5);
hTokenAmount = hTokenAmount.add(canhAmount);
uint256 befhBal = hToken.balanceOf(address(this));
swapTokensFor(canhAmount,address(hToken),address(this));
uint256 newhBal = hToken.balanceOf(address(this)).sub(befhBal);
hDivTokenAmount = hDivTokenAmount.add(newhBal);
isHProc = true;
}
}
This makes it possible for an attacker to manipulate the price and force the contract to buy tokens at a high price. In this case, the attacker influenced the price of GVC by requesting a flashloan of BUSD and buying up all the available liquidity of GVC in the pool and then calling transfer() on Seaman.
- Prevent users to manipulate contract balances via low liquidity pair interactions.
- Do not automatically perform trades without a sanity check on the prices
- MBC Token - Same
swapAndLiquify pattern exploited via price manipulation
- BVaults - Price manipulation through unprotected swap functions
- Four Meme - Preemptive pool price manipulation on BSC