FairSide contest - shw's results

FairSide Network

General Information

Platform: Code4rena

Start Date: 20/05/2021

Pot Size: $55,000 USDC

Total HM: 19

Participants: 8

Period: 7 days

Judge: cemozer

Total Solo HM: 11

Id: 11

League: ETH

FairSide

Findings Distribution

Researcher Performance

Rank: 2/8

Findings: 10

Award: $14,513.79

🌟 Selected for report: 7

🚀 Solo Findings: 2

Findings Information

🌟 Selected for report: 0xRajeev

Also found by: shw

Labels

bug
3 (High Risk)
sponsor confirmed
disagree with severity
resolved

Awards

2161.6853 USDC - $2,161.69

External Links

Handle

shw

Vulnerability details

Impact

Users have to pay twice the FSD tokens when tokenizing their convictions if the locked variable is non-zero.

Proof of Concept

The first payment is made in the function tokenizeConviction of the contract ERC20ConvictionScore (line 282), where a user transfer locked amount of FSD to the contract fairSideConviction. The second payment is made afterward when the function createConvictionNFT is called (line 304). At line 50 of the contract fairSideConviction, the user transfers locked amount of FSD to the contract fairSideConviction again.

Similarly, when a user calls acquireConviction, he received the amount of locked token twice. The first is at line 123 in the contract FairSideConviction, and the second is at line 316 in the contract ERC20ConvictionScore.

Referenced code: When tokenizing convictions: ERC20ConvictionScore.sol#L282 ERC20ConvictionScore.sol#L304 FairSideConviction.sol#L50

When acquiring convictions: ERC20ConvictionScore.sol#L314 ERC20ConvictionScore.sol#L316 FairSideConviction.sol#L123

Should only charge or send FSD tokens once. Consider removing the logic at line 281-285 and 316 in the contract ERC20ConvictionScore.

#0 - fairside-core

2021-05-30T12:55:16Z

While this is 100% an issue, I believe the high severity label is not accurate because there is no fund loss. While funds are locked twice, they are also unlocked twice which means the full NFT workflow is simply overcharging rather than causing funds to be lost etc.

As such, I believe this is a 1 (minor) finding as it merely doubles the actual funds that would otherwise be locked and unlocked in the NFT process. I would also be fine with a 2 (medium), however, I believe medium is a stretch and high is definitely disputed.

#1 - fairside-core

2021-05-30T19:21:19Z

Fixed in PR#3.

#2 - cemozerr

2021-06-07T20:18:08Z

Duplicate of issue #29 and #30. Labeling this as 2 high-risk issues as they do result to loss of funds even if temporary.

Findings Information

🌟 Selected for report: shw

Also found by: a_delamo, cmichel, pauliax

Labels

bug
3 (High Risk)
sponsor confirmed
resolved

Awards

1297.0112 USDC - $1,297.01

External Links

Handle

shw

Vulnerability details

Impact

The variable pendingWithdrawals in the contract Withdrawable is not decreased after the function withdraw is called, which causes the return value of function getReserveBalance less than it should be. This bug could cause incorrect results in several critical functions related to FSD token pricing, including getFSDPrice, purchaseMembership, getMaximumBenefitPerUser, mint, and burn in the FSDNetwork and FSD contracts.

Proof of Concept

Referenced code: Withdrawable.sol#L14-L19 Withdrawable.sol#L26-L28

Affected functions: FSD.sol#L85 FSD.sol#L100 FSDNetwork.sol#L136 FSDNetwork.sol#L361 FSDNetwork.sol#L369

Add pendingWithdrawals = pendingWithdrawals.sub(reserveAmount); after line 17 in the contract Withdrawable.

#0 - fairside-core

2021-05-30T13:10:24Z

One of two easter eggs!

#1 - fairside-core

2021-05-30T19:32:22Z

Fixed in PR#5.

Findings Information

🌟 Selected for report: shw

Also found by: cmichel

Labels

bug
3 (High Risk)
sponsor confirmed
resolved

Awards

2161.6853 USDC - $2,161.69

External Links

Handle

shw

Vulnerability details

Impact

The function _calculateDeltaOfFSD of contract ABC incorrectly converts an int256 type parameter, _reserveDelta, to uint256 by explicit conversion, which in general results in an extremely large number when the provided parameter is negative. The extremely large number could cause a SafeMath operation sub at line 43 to revert, and thus the FSD tokens cannot be burned. (_reserveDelta is negative when burning FSD tokens)

Proof of Concept

Simply calling fsd.burn after a successful fsd.mint will trigger this bug.

Referenced code: ABC.sol#L43 ABC.sol#L49 ABC.sol#L54

Use the solidity function abs to get the _reserveDelta absolute value.

#0 - fairside-core

2021-05-30T16:58:10Z

Fixed in PR#1.

Findings Information

🌟 Selected for report: shw

Labels

bug
3 (High Risk)
sponsor confirmed
resolved

Awards

4803.7452 USDC - $4,803.75

External Links

Handle

shw

Vulnerability details

Impact

The current implementation of the arctan formula in the contract FairSideFormula is inconsistent with the referenced paper and could cause incorrect results when the input parameter is negative. The erroneous formula affects the function calculateDeltaOfFSD and the number of FSD tokens minted or burned.

Proof of Concept

The function _arctan misses two abs on the variable a. The correct implementation should be:

function _arctan(bytes16 a) private pure returns (bytes16) {
    return
        a.mul(PI_4).sub(
            a.mul(a.abs().sub(ONE)).mul(APPROX_A.add(APPROX_B.mul(a.abs())))
        );
}

Notice that _arctan is called by arctan, and arctan is called by arcs with ONE.sub(arcInner) provided as the input parameter. Since arcInner = MULTIPLIER_INNER_ARCTAN.mul(x).div(fS3_4) can be a large number (recall that x is the capital pool), it is possible that the parameter a is negative.

Referenced code:

FairSideFormula.sol#L45-L61 FairSideFormula.sol#L77-L85 FairSideFormula.sol#L127 ABC.sol#L38

Modify the _arctan function as above.

#0 - fairside-core

2021-05-30T19:26:35Z

Fixed in PR#4.

Findings Information

🌟 Selected for report: 0xRajeev

Also found by: gpersoon, pauliax, shw

Labels

bug
duplicate
2 (Med Risk)

Awards

262.6448 USDC - $262.64

External Links

Handle

shw

Vulnerability details

Impact

The addRegistrationTributeGovernance function in the contract FSD includes an incorrect function, _addTribute. According to its function name, the called function should be _addGovernanceTribute instead.

Proof of Concept

Referenced code: FSD.sol#L140

Should change _addTribute to _addGovernanceTribute.

#0 - fairside-core

2021-05-30T13:27:27Z

Duplicate of #20

Findings Information

🌟 Selected for report: shw

Also found by: cmichel, pauliax, s1m0

Labels

bug
2 (Med Risk)
sponsor confirmed
resolved

Awards

262.6448 USDC - $262.64

External Links

Handle

shw

Vulnerability details

Impact

The getEtherPrice function in the contract FSDNetwork fetches the ETH price from a Chainlink aggregator using the latestRoundData function. However, there are no checks on roundID nor timeStamp, resulting in stale prices.

Proof of Concept

Referenced code: FSDNetwork.sol#L376-L381

Add checks on the return data with proper revert messages if the price is stale or the round is uncomplete, for example:

(uint80 roundID, int256 price, , uint256 timeStamp, uint80 answeredInRound) = ETH_CHAINLINK.latestRoundData();
require(answeredInRound >= roundID, "...");
require(timeStamp != 0, "...");

#0 - fairside-core

2021-05-30T19:45:13Z

Fixed in PR#7.

#1 - cemozerr

2021-06-08T19:42:55Z

Labeling this as medium risk as stale ether price could put funds at risk.

Findings Information

🌟 Selected for report: shw

Labels

bug
1 (Low Risk)
sponsor confirmed
disagree with severity
resolved

Awards

1441.1236 USDC - $1,441.12

External Links

Handle

shw

Vulnerability details

Impact

The variable fShareRatio in the function purchaseMembership of contract FSDNetwork is vulnerable to manipulation by flash minting and burning, which could affect several critical logics, such as the check of enough capital in the pool (line 139-142) and the staking rewards (line 179-182).

Proof of Concept

The fShareRatio is calculated (line 136) by:

(fsd.getReserveBalance() - totalOpenRequests).mul(1 ether) / fShare;

where fsd.getReserveBalance() can be significantly increased by a user minting a large amount of FSD tokens with flash loans. In that case, the increased fShareRatio could affect the function purchaseMembership results. For example, the user could purchase the membership even if the fShareRatio is < 100% previously, or the user could earn more staking rewards than before to reduce the membership fees. Although performing flash minting and burning might not be profitable overall since a 3.5% tribute fee is required when burning FSD tokens, it is still important to be aware of the possible manipulation of fShareRatio.

Referenced code: FSDNetwork.sol#L134-L142 FSDNetwork.sol#L178-L182

Force users to wait for (at least) a block to prevent flash minting and burning.

#0 - fairside-core

2021-05-30T12:46:52Z

I believe this to be a minor (1) or none (0) severity issue given that the manipulation of fShareRatio is unsustainable due to the fee and the example given is actually not possible. If I affect fShareRatio to go above 100% to purchase a membership, I will be unable to burn the necessary FSD to go below 100% again as burning is disabled when the ratio is or would go to below 100%.

#1 - fairside-core

2021-05-30T17:16:05Z

Fixed in PR#2.

#2 - cemozerr

2021-06-08T20:22:45Z

Labeling this as low risk as 3.5% tribute fee makes it very unlikely that these flash minting will be profitable.

Findings Information

🌟 Selected for report: 0xRajeev

Also found by: shw

Labels

bug
1 (Low Risk)
disagree with severity
sponsor acknowledged

Awards

216.1685 USDC - $216.17

External Links

Handle

shw

Vulnerability details

Impact

In most contracts, the pragma statements are declared as pragma solidity >=0.6.0 <0.8.0;, which are unlocked and could cause the contracts to accidentally be compiled or deployed using an outdated or buggy compiler version.

Proof of Concept

Referenced code: Please use grep -R pragma . to find the unlocked pragma statements.

Should lock pragmas to a specific compiler version. Besides, consider the known compiler bugs in the following references and check whether the contracts include those bugs.

Solidity compiler bugs: Solidity repo - known bugs Solidity repo - bugs by version

#0 - fairside-core

2021-05-30T13:33:37Z

The pragma statements were left unlocked to allow flexibility in development. I believe that since this is not a functional finding, it should be marked as 0 (non-critical).

#1 - cemozerr

2021-06-07T20:09:26Z

Duplicate of https://github.com/code-423n4/2021-05-fairside-findings/issues/25. Labeling it as low risk as it could indeed cause the contracts to accidentally be compiled or deployed using an outdated or buggy compiler version

Findings Information

🌟 Selected for report: shw

Labels

bug
question
2 (Med Risk)
disagree with severity

Awards

480.3745 USDC - $480.37

External Links

Handle

shw

Vulnerability details

Impact

Users can pay fewer FSD tokens when purchasing a membership or opening a cost share request by flash minting and burning FSD tokens, which could significantly affect the FSD spot price.

Proof of Concept

The function getFSDPrice returns the current FSD price based on the reserves in the capital pool (see lines 353-364 in contract FSDNetwork). Notice that when minting and burning FSD tokens, the fsd.getReserveBalance() increases but not the fShare. Therefore, according to the pricing formula, FairSideFormula.f, the FSD price increases when minting, and vice versa, decreases when burning.

When purchasing a membership, the number of FSD tokens that a user should pay is calculated based on the current FSD price, which is vulnerable to manipulation by flash minting and burning. Consider a user performing the following actions (all are done within a single transaction or flashbot bundle):

  1. The user mints a large number of FSD (by using flash loans) to raise the current FSD price.
  2. The user purchases a membership by calling purchaseMembership. Since the price of FSD is relatively high, the user pays fewer FSD tokens for the membership fee than before.
  3. The user burns the previously minted FSD tokens, losing 3.5% of his capital for the tribute fees.

Although the user pays for the 3.5% tribute fees, it is still possible to make a profit. Suppose that the price of FSD to ETH is p_1 and p_2 before and after minting, respectively. The user purchases a membership with x ETH costShareBenefit, and uses y ETH to flash mint the FSD tokens. In a regular purchase, the user pays 0.04x / p_1 FSD tokens, equivalent to 0.04x ETH. By performing flash mints and burns, the user pays 0.04x / p_2 FSD tokens, which is, in fact, equivalent to 0.04x * p_1 / p_2 ETH. He also pays 0.035y ETH for tribute fees. The profit user made is 0.04x * (1 - p1 / p2) - 0.035y (ETH), where p2 and y are dependent to each other but independent to x. Thus, the profit can be positive if costShareBenefit is large enough.

The same vulnerability exists when a user opens a cost share request, where the bounty to pay is calculated based on the current price of FSD tokens.

Referenced code: FSDNetwork.sol#L353-L364 FSDNetwork.sol#L145-L146 FSDNetwork.sol#L217-L222

Force users to wait for (at least) a block to prevent flash minting and burning.

#0 - fairside-core

2021-05-30T12:41:17Z

The issue relies on costShareBenefit being large enough which is inherently limited to a % of the capital pool, meaning that the arbitrage opportunity present here is inexistent or highly unlikely to be beneficial. Can we reach out to the submitter to request them to prove that even with the costShareBenefit % limit this is a sustainable attack by providing us with numbers? If no such numbers are present, I would decrease the severity of this to either minor (1) or none (0).

#1 - cemozerr

2021-06-08T20:18:32Z

Will wait for a proof from the auditer, shw, for this one.

#2 - x9453

2021-06-12T09:38:32Z

Hi, thanks for giving me a chance to clarify this finding.

After realizing that a user's costShareBenefit is limited to a % of the capital pool (5% as specified in the code), I would say this attack is not successful according to the following estimation of the upper-bound of user's profit:

User's profit = 0.04x * (1 - p1 / p2) - 0.035y <= 0.04x - 0.035y <= 0.04 * 0.05 * (z + y) - 0.035y = 0.002z - 0.033y

where z is the amount of ETH in the capital pool before minting. A negative coefficient of y implies that using a flash loan does not help to increase the profit but to decrease it.

#3 - x9453

2021-06-14T08:50:50Z

After some thoughts, I think the estimation should also consider how flash loan affects on the FSD's price to be more accurate. According to the price formula, we have p1 = A + z^4 / (C * fShare^3) and p2 = A + (z + y)^4 / (C * fShare^3) (assuming the best case, where fShare does not increase). Let r = y / z, the ratio of flash loan to the capital pool, then we can approximate p1 / p2 = z^4 / (z + y)^4 = 1 / (r + 1)^4. Therefore,

User's profit = 0.04x * (1 - p1 / p2) - 0.035y = 0.04 * 0.05 * (z + y) * (1 - 1 / (r + 1)^4) - 0.035y = (0.002 * (r + 1) * (1 - 1 / (r + 1)^4) - 0.035r) * z = f(r) * z

WolframAlpha tells us that f(r) < 0 for all r > 0, meaning that the user does not make a profit no matter how much flash loan he borrowed.

It is worth mentioning that different % of withdrawal fee, cost share benefit limit, and tribute fee could lead to different results. That is, the constants, 0.002 and 0.035, determine whether user's profit can be positive (i.e., there exists r > 0 s.t. f(r) > 0). Further calculation shows that this happens iff the product of the withdrawal fee and cost share benefit limit is greater than the tribute fee divided by 4, which is unlikely in normal settings. Please let me know if you need more details or a PoC on this.

Findings Information

🌟 Selected for report: shw

Labels

bug
question
G (Gas Optimization)
sponsor confirmed
resolved

Awards

0 USDC - $0.00

External Links

Handle

shw

Vulnerability details

Impact

Gas optimization is possible for the current rootPows implementation.

Proof of Concept

The original implementation of rootPows requires 4 mul and 2 sqrt:

function rootPows(bytes16 x) private pure returns (bytes16, bytes16) {
    // fourth root
    x = x.sqrt().sqrt();
    // to the power of 3
    x = _pow3(x);
    // we offset the root on the second arg
    return (x, x.mul(x));
}

However, the calculation process can be simplified to be more gas-efficient than the original with only 1 mul and 2 sqrt requried:

function rootPows(bytes16 x) private pure returns (bytes16, bytes16) {
    bytes16 x1_2 = x.sqrt();
    bytes16 x3_2 = x.mul(x1_2);
    bytes16 x3_4 = x3_2.sqrt();
    return (x3_4, x3_2);
}

Referenced code: FairSideFormula.sol#L67-L75

To save gas, change the implementation of rootPows as mentioned above.

#0 - fairside-core

2021-05-30T13:13:14Z

Optimization is confirmed (basically constructs x^3/2 then applies root on it). Given that this is a gas optimization, perhaps the severity should be noted down to 1? I'll leave this up to the judges.

#1 - fairside-core

2021-05-30T19:36:55Z

Fixed in PR#6.

#2 - cemozerr

2021-06-07T20:15:26Z

Labeling this as gas optimization.

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter