Slow Tan Swallow
High
Users use buyVotes
to vote for another user and for such they pay for every vote in ETH.
function buyVotes(...) public payable whenNotPaused activeMarket(profileId) nonReentrant {
_checkMarketExists(profileId);
// Determine how many votes can be bought with the funds provided
(
uint256 votesBought,
uint256 fundsPaid,
,
uint256 protocolFee,
uint256 donation,
uint256 minVotePrice,
uint256 maxVotePrice
) = _calculateBuy(markets[profileId], isPositive, msg.value);
The price, votes and the amount this user would pay is calculated inside _calculateBuy
, where we would take note that returned fundsPaid
is all of the ETH used to buy votes + both protocolFee
and donation
fees
fundsPaid += protocolFee + donation;
maxPrice = votePrice;
return (votesBought, fundsPaid, votePrice, protocolFee, donation, minPrice, maxPrice);
}
Later when we get back into buyVotes
we can see that an issue appears. We send the appropriate fees using applyFees(protocolFee, donation, profileId);
, however at the end of the function we also increase marketFunds[profileId]
by fundsPaid
. From _calculateBuy
we have seen that fundsPaid
already accounts protocolFee
and donation
, but these are already sent, meaning they they are accounted twice! Once send in applyFees
and then added to marketFunds
_checkSlippageLimit(votesBought, expectedVotes, slippageBasisPoints);
// Send fees
applyFees(protocolFee, donation, profileId);
markets[profileId].votes[isPositive ? TRUST : DISTRUST] += votesBought;
votesOwned[msg.sender][profileId].votes[isPositive ? TRUST : DISTRUST] += votesBought;
if (!isParticipant[profileId][msg.sender]) {
participants[profileId].push(msg.sender);
isParticipant[profileId][msg.sender] = true;
}
uint256 refund = msg.value - fundsPaid;
if (refund > 0) _sendEth(refund);
// Buy increase `marketFunds` by `fundsPaid + protocolFee + donation` fees ?
marketFunds[profileId] += fundsPaid;
marketFunds
being increased by protocolFee
and donation
, instead of only the real votes value.
marketFunds[profileId] += fundsPaid;
No response
No response
No response
Insolvency since the contract holds all ETH balances, meaning if we send the fees and later withdrawGraduatedMarketFunds
is called, we would have taken more ETH out than we attributed inside the internal accounting. This ETH will come from other profile votes, eventually leading to insolvency.
No response
Consider these 2 changes
Inside _calculateBuy:
while (fundsAvailable >= votePrice) {
fundsAvailable -= votePrice;
fundsPaid += votePrice;
votesBought++;
market.votes[isPositive ? TRUST : DISTRUST] += 1;
votePrice = _calcVotePrice(market, isPositive);
}
// In order for `fundsPaid` to be only the value paid for buying votes
- fundsPaid += protocolFee + donation;
maxPrice = votePrice;
Inside buyVotes:
- uint256 refund = msg.value - fundsPaid;
+ uint256 refund = msg.value - (fundsPaid +donation + protocolFee) ;
if (refund > 0) _sendEth(refund);
emit VotesBought(
profileId,
msg.sender,
isPositive,
votesBought,
- fundsPaid
+ fundsPaid + donation + protocolFee, // So the event is emitted by the old standard (all funds used)
block.timestamp,
minVotePrice,
maxVotePrice
);
This way the amounts will be calculated correctly, while at the same time the user will be refunded what he is supposed to.