https://github.com/hackenproof-public/reliq-protocol
When enterTheTemple() is called, external attackers can sandwich the transaction,
causing users to receive significantly less relHYPE than expected. This results in
direct theft of user funds by MEV bots.
The vulnerability is in the missing slippage parameter not in who can call the function. Even admin functions should protect users from external attacks.
function enterTheTemple() external onlyOwner nonReentrant {
// ...
uint256 totalBacking = backingToken.balanceOf(address(this));
backingToken.approve(address(reliq), totalBacking);
reliq.buy(address(this), totalBacking); // No minimum output specified!
totalReliqHYPEAcquired = reliq.balanceOf(address(this));
// No check: require(totalReliqHYPEAcquired >= minExpected, "Too much slippage");
}
Why this matters: An attacker watching the mempool can sandwich this transaction:
The users are the ones who lose out they deposited 100 ETH worth of tokens but only get claims on maybe 70 ETH worth of relHYPE. The attacker stole 30 ETH of value.
Example: Let's say users deposited 1000 tokens total:
Add a minimum output parameter:
function enterTheTemple(uint256 minReliqHYPE) external onlyOwner nonReentrant {
require(block.timestamp >= deadline, "Altar: deadline not passed");
require(!isReliqHYPEClaim, "Altar: ReliqHYPE claim already done");
uint256 totalBacking = backingToken.balanceOf(address(this));
backingToken.approve(address(reliq), totalBacking);
reliq.buy(address(this), totalBacking);
totalReliqHYPEAcquired = reliq.balanceOf(address(this));
require(totalReliqHYPEAcquired >= minReliqHYPE, "Altar: slippage too high"); // ADD THIS
isReliqHYPEClaim = true;
emit TempleEntered(totalBacking, totalReliqHYPEAcquired);
}
This is a real attack vector that's been exploited in DeFi before. Without slippage protection, users are sitting ducks for MEV bots.
enterTheTemple() in mempoolenterTheTemple() executes at bad pricePaste this test code in test\PreDeposit\Altar.t.sol
function test_NoSlippageProtection() public {
console.log("=== No Slippage Protection ===");
// Alice sacrifices
vm.startPrank(alice);
backing.approve(address(altar), 100 ether);
altar.sacrifice(100 ether, alice);
vm.stopPrank();
vm.warp(altar.deadline() + 1);
uint256 backingAmount = backing.balanceOf(address(altar));
console.log("Backing to spend:", backingAmount);
// Owner enters temple (no min output specified)
altar.enterTheTemple();
uint256 reliqReceived = altar.totalReliqHYPEAcquired();
console.log("ReliqHYPE received:", reliqReceived);
// In our mock, we get 2x
// But in reality, could get much less due to:
// - Price manipulation
// - Sandwich attacks
// - Fees
console.log("MEDIUM: No minimum relHYPE check - vulnerable to sandwich attacks");
// If reliq.buy() returned 0, it would still succeed!
assertTrue(reliqReceived > 0, "In this mock, received tokens");
}
Run the test code:
forge test --match-test test_NoSlippageProtection -vv
Result Output
Ran 1 test for test/PreDeposit/Altar.t.sol:AltarTest
[PASS] test_NoSlippageProtection() (gas: 298301)
Logs:
=== No Slippage Protection ===
Backing to spend: 100000000000000000000
ReliqHYPE received: 200000000000000000000
MEDIUM: No minimum relHYPE check - vulnerable to sandwich attacks
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 10.36ms (3.83ms CPU time)
Ran 1 test suite in 61.90ms (10.36ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)