Skip to content

Latest commit

 

History

History
139 lines (115 loc) · 7.64 KB

File metadata and controls

139 lines (115 loc) · 7.64 KB

Recumbent Shamrock Barracuda

High

Attacker can gain a advantage by manipulating the order in which votes are bought and sold.

Summary

The current implementation of the voting market allows an attacker to profit by alternately buying TRUST and DISTRUST votes one at a time and then selling them in bulk. The vulnerability is due to the way _calcVotePrice recalculates the vote price during each transaction. Since the bonding curve mechanism redistributes the price based on the relative number of votes, an attacker can exploit the price difference by manipulating the market state. https://github.com/sherlock-audit/2024-11-ethos-network-ii/blob/main/ethos/packages/contracts/contracts/ReputationMarket.sol#L920

Root Cause

The root cause of the vulnerability lies in the design of the bonding curve mechanism and the lack of safeguards against rapid alternation between buying and selling votes. Specifically:

  1. Bonding Curve Behavior:

    • The _calcVotePrice function calculates vote prices dynamically based on the relative count of TRUST and DISTRUST votes using the bonding curve formula:
      price = (votes * basePrice) / totalVotes;
    • Each purchase or sale affects the price of both vote types (TRUST and DISTRUST), creating opportunities for manipulation by alternately buying and selling to exploit the shifting price.
  2. No Restriction on Alternating Trades:

    • The protocol does not impose restrictions on alternating between TRUST and DISTRUST trades. This allows an attacker to execute rapid trades in a specific pattern to exploit the price differential caused by their actions.
  3. Bulk Sale Vulnerability:

    • Selling votes in bulk does not fully reflect the incremental price reduction that should occur during the sale process. Instead, the system overvalues the votes being sold, leading to excessive profits for the attacker.

By leveraging these design gaps, an attacker can systematically exploit the protocol to extract profits while depleting protocol funds.

Impact

  • Profit extraction: An attacker can extract profits by alternately buying and selling votes at manipulated price points.
  • Funding depletion: Repeated exploitation can deplete the protocol’s funds.

Attack Path

  • Assume that initialVotes are like this: market.votes[TRUST] = 1 market.votes[DISTRUST] = 1
  • Attacker buys votes as follows: 1 TRUST, 1 DISTRUST, 1 TRUST, 1 DISTRUST ... =>(1 TRUST, 1 DISTRUST) * 10
  • Attacker sells votes as follows: 10 TRUST, 10 DISTRUST
  • The funds the attacker receives by selling are greater than the funds spent by buying and the buying fee and selling fee.

In the example, the number of initial vote was set to 1 according to test code, but this attack is possible in any case.

PoC

  • Assume that initialVotes and market.basePrice are like this: market.votes[TRUST] = 1 market.votes[DISTRUST] = 1 market.basePrice = 10000 (For convenience of calculation)
  • Set all feeBasePoints at max: entryProtocolFeeBasisPoints=500 exitProtocolFeeBasisPoints=500 donationBasisPoints=500
  • Total funds for buying with attack path:
  1. buy 1 TRUST: (Since a 10% fee is taken from the funds at first to buy votes, the total funds required is equal to the vote value divided by 0.9.) votePrice = (1 * 10000) / 2 = 5000 totalFund = votePrice + protocolFee + donation = 5000 / 0.9 = 5556 As result, TRUST=2, DISTRUST=1;
  2. buy 1 DISTRUST: votePrice = (1 * 10000) / 3 = 3334 totalFund = 3334 / 0.9 = 3705 As result, TRUST=2, DISTRUST=2;
  3. buy 1 TRUST: votePrice = (2 * 10000) / 4 = 5000 totalFund = 5000 / 0.9 = 5556 As result, TRUST=3, DISTRUST=2;
  4. buy 1 DISTRUST: votePrice = (2 * 10000) / 5 = 4000 totalFund = 5000 / 0.9 = 4445 As result, TRUST=3, DISTRUST=3; ... Total Funds = (1 * 10000) / 2 / 0.9 + (1 * 10000) / 3 / 0.9 + (2 * 10000) / 4 / 0.9 + (2 * 10000) / 5 / 0.9 + ... + (10 * 10000) / 20 / 0.9 + (10 * 10000) / 21 / 0.9 = 104541
  • Total funds from selling with attack path:
  1. sell 10 TRUST: (After the votePrice is calculated, a 5% fee is charged, so the funds you receive are equal to the vote value multiplied by 0.95.) votePrice = (10 * 10000) / 21 + (9 * 10000) / 20 + ... (1 * 10000) / 12 = 31192 totalFund = votePrice - protocolFee = 31192 * 0.95 = 29632 As result, TRUST= 1, DISTRUST=11;
  2. sell 10 DISTRUST: votePrice = (10 * 10000) / 11 + (9 * 10000) / 10 + ... (1 * 10000) / 2 = 79798 totalFund = 79798 * 0.95 = 75808 As result, TRUST= 1, DISTRUST=1; Total Funds = 29632 + 75808 = 105440
  • Even though the number of votes before and after the purchase is the same, the attacker gains and the protocol loses from the difference between the funds spent on the purchase and the funds received upon the purchase(Users only need to have enough funds to buy votes). diff = 105440 - 104541 = 899 Attacker can make a profit of around 9% by conducting a single attack with funds of around 10.5 times the market.basePrice.

Attacker can attack with less funds by buying and selling as follows:

1=TRUST vote, 0=DISTRUST vote (1, 1) => (4, 7)

buy: 0, 1, 0, 1, 0, 1, 0 * 3 => totalFund = [(1 * 10000) / 2 / 0.9 + (1 * 10000) / 3 / 0.9 + (2 * 10000) / 4 / 0.9 + (2 * 10000) / 5 / 0.9 + (3 * 10000) / 6 / 0.9 + (3 * 10000) / 7 / 0.9] + [(4 * 10000) / 8 + (5 * 10000) / 9 + (6 * 10000) / 10] / 0.9 = 47,967 sell: 1 * 3, 0 * 6 => totalFund = [(3 * 10000) / 10 + (2 * 10000) / 9 + (1 * 10000) / 8] * 0.95 + [(6 * 10000) / 7 + (5 * 10000) / 6 + (4 * 10000) / 5 + (3 * 10000) / 4 + (2 * 10000) / 3 + (1 * 10000) / 2] * 0.95 = 48,014 diff = 48,014 - 47,967 = 47 Attacker can make a profit of around 0.5% by conducting a single attack with funds of around 4.8 times the market.basePrice.

If the initialVote is 2, attacker can attack like this: (2, 2) => (9, 16) buy: (0, 1) * 7, 0 * 7 sell: 1 * 7, 0 * 14 Attacker can make a profit of around 0.8% by conducting a single attack with funds of around 11.8 times the market.basePrice.

If the initialVote is 5, attacker can attack like this: (5, 5) => (25, 45) buy: (0, 1) * 20, 0 * 20 sell: 1 * 20, 0 * 40 Attacker can make a profit of around 5.1% by conducting a single attack with funds of around 32.8 times the market.basePrice.

The larger the initialVote, the more votes need to be purchased for an attack, but with enough initial funds for that, it is possible to steal all the protocol's funds by repeating the attack multiple times. The attack is possible even if the number of TRUST and DISTRUST votes is an any value different from the initialVote

(2, 4) => (7, 15) buy: 1 * 2, (0, 1) * 3, 0 * 8 sell: 1 * 5, 0 * 11 Attacker can make a profit of around 4.8% by conducting a single attack with funds of around 9.3 times the market.basePrice.

Mitigation

  • Changes the pricing logic when selling votes.: The selling price is calculated based on the state at the time the vote is sold (i.e. 1 is deducted).
function _calculateSell() {
   ...
---   market.votes[isPositive ? TRUST : DISTRUST] -= 1;
---   votePrice = _calcVotePrice(market, isPositive);
+++   votePrice = _calcVotePriceForSelling(market, isPositive);
+++   market.votes[isPositive ? TRUST : DISTRUST] -= 1;
   ...
}
function _calcVotePriceForSelling(Market memory market, bool isPositive) private pure returns (uint256) {
    uint256 totalVotes = market.votes[TRUST] + market.votes[DISTRUST];
    return ((market.votes[isPositive ? TRUST : DISTRUST] - 1) * (market.basePrice)) / totalVotes;
  }
  • Charge Fees on Both Buy and Sell: Apply a higher fee for frequent trading to discourage exploitation