Orbiting Steel Tardigrade
High
In the buyVotes()
function, the protocol calls the _checkSlippageLimit()
function to implement slippage protection.
function buyVotes(
uint256 profileId,
bool isPositive,
uint256 expectedVotes,
uint256 slippageBasisPoints
) public payable whenNotPaused {
_checkMarketExists(profileId);
_checkAddressHasProfile();
// determine how many votes can be bought with the funds provided
(uint256 votesBought, uint256 fundsPaid, ) = _calculateBuy(
markets[profileId],
isPositive,
msg.value
);
_checkSlippageLimit(votesBought, expectedVotes, slippageBasisPoints);
However, in the sellVotes()
function, there are no slippage protection measures in place, making it vulnerable to sandwich attacks during the selling process.
function sellVotes(uint256 profileId, bool isPositive, uint256 amount) public whenNotPaused {
_checkMarketExists(profileId);
_checkAddressHasProfile();
// calculate the amount of votes to sell and the funds received
(uint256 votesSold, uint256 fundsReceived, ) = _calculateSell(
markets[profileId],
profileId,
isPositive,
amount
);
// update the market state
markets[profileId].votes[isPositive ? TRUST : DISTRUST] -= votesSold;
votesOwned[msg.sender][profileId].votes[isPositive ? TRUST : DISTRUST] -= votesSold;
// Check if seller has no votes left after selling
if (
votesOwned[msg.sender][profileId].votes[TRUST] == 0 &&
votesOwned[msg.sender][profileId].votes[DISTRUST] == 0
) {
isParticipant[profileId][msg.sender] = false;
}
// send the proceeds to the seller
payable(msg.sender).transfer(fundsReceived);
emit VotesSold(profileId, msg.sender, isPositive, votesSold, fundsReceived, block.timestamp);
_emitMarketUpdate(profileId);
}
In the sellVotes() function, there are no slippage protection measures https://github.com/sherlock-audit/2024-10-ethos-network/blob/main/ethos/packages/contracts/contracts/ReputationMarket.sol#L233-L261
No response
No response
- A regular user prepares to call the
sellVotes()
function to sell their votes. - A malicious user notices the regular user's intent and sells their votes in advance, driving down the price. After the regular user sells, due to the lack of slippage protection, the selling result is unfavorable. The regular user then buys back their votes at a lower price, effectively obtaining more votes.
The result of the user's vote sale is not as expected.
function sellVotes(uint256 profileId, bool isPositive, uint256 amount) public whenNotPaused {
_checkMarketExists(profileId);
_checkAddressHasProfile();
// calculate the amount of votes to sell and the funds received
(uint256 votesSold, uint256 fundsReceived, ) = _calculateSell(
markets[profileId],
profileId,
isPositive,
amount
);
// update the market state
markets[profileId].votes[isPositive ? TRUST : DISTRUST] -= votesSold;
votesOwned[msg.sender][profileId].votes[isPositive ? TRUST : DISTRUST] -= votesSold;
// Check if seller has no votes left after selling
if (
votesOwned[msg.sender][profileId].votes[TRUST] == 0 &&
votesOwned[msg.sender][profileId].votes[DISTRUST] == 0
) {
isParticipant[profileId][msg.sender] = false;
}
// send the proceeds to the seller
payable(msg.sender).transfer(fundsReceived);
emit VotesSold(profileId, msg.sender, isPositive, votesSold, fundsReceived, block.timestamp);
_emitMarketUpdate(profileId);
}
Recommended fix: add slippage protection.