Spartan Protocol contest - shw's results

Community-governed token to incentivize deep liquidity pools for leveraged synthetic token generation.

General Information

Platform: Code4rena

Start Date: 15/07/2021

Pot Size: $80,000 USDC

Total HM: 28

Participants: 18

Period: 7 days

Judge: ghoulsol

Total Solo HM: 18

Id: 20

League: ETH

Spartan Protocol

Findings Distribution

Researcher Performance

Rank: 4/18

Findings: 7

Award: $6,414.99

🌟 Selected for report: 3

🚀 Solo Findings: 1

Findings Information

🌟 Selected for report: hickuphh3

Also found by: 0xRajeev, cmichel, jonah1005, shw

Labels

bug
duplicate
3 (High Risk)

Awards

517.27 USDC - $517.27

External Links

Handle

shw

Vulnerability details

Impact

The _approve functions of the pool LP tokens and synths do nothing if the _allowances is already the maximum number, i.e., type(uint256).max. Therefore, Alice cannot change her allowance to Bob once she approved him with the maximum approval.

Proof of Concept

Referenced code: Pool.sol#L99 Synth.sol#L93

Consider removing the _allowances[owner][spender] < type(uint256).max condition of _approve to allow users to reset their allowance to others even if it is the maximum.

#0 - SamusElderg

2021-07-25T11:49:50Z

Duplicate of #29

Findings Information

🌟 Selected for report: gpersoon

Also found by: 0xRajeev, 7811, JMukesh, cmichel, heiho1, jonah1005, k, maplesyrup, shw, zer0dot

Labels

bug
duplicate
3 (High Risk)

Awards

124.9539 USDC - $124.95

External Links

Handle

shw

Vulnerability details

Impact

The return values of BEP20.transfer and BEP20.transferFrom are not checked to be true in multiple contracts. The return value could be false if the transferred token is not BEP20-compliant, indicating that the transfer fails, while the calling contract will not notice the failure. As written in the BEP20 specification:

Callers MUST handle false from returns (bool success). Callers MUST NOT assume that false is never returned!

Proof of Concept

The return value check should be applied, especially when the transferred token is not created by the Spartan protocol (and thus could be non-compliant to BEP20):

Unchecked transfer: Pool.sol#L199 Pool.sol#L224 Router.sol#L67 Router.sol#L126 Router.sol#L221

Unchecked transferFrom: poolFactory.sol#L112 Router.sol#L207 Synth.sol#L204 Dao.sol#L266

Use the SafeBEP20 implementation from PancakeSwap (or SafeERC20 from OpenZeppelin) and call safeTransfer or safeTransferFrom when transferring BEP20 tokens.

#0 - SamusElderg

2021-07-26T01:46:23Z

Duplicate of #8

Findings Information

🌟 Selected for report: shw

Labels

bug
3 (High Risk)
sponsor confirmed
disagree with severity

Awards

3942.0057 USDC - $3,942.01

External Links

Handle

shw

Vulnerability details

Impact

The getPoolShareWeight function returns a user's pool share weight by calculating how many SPARTAN the user's LP tokens account for. However, this approach is vulnerable to flash loan manipulation since an attacker can swap a large number of TOKEN to SPARTAN to increase the number of SPARTAN in the pool, thus effectively increasing his pool share weight.

Proof of Concept

According to the implementation of getPoolShareWeight, a user's pool share weight is calculated by uints * baseAmount / totalSupply, where uints is the number of user's LP tokens, totalSupply is the total supply of LP tokens, and baseAmount is the number of SPARTAN in the pool. Thus, a user's pool share weight is proportional to the number of SPARTAN in the pool. Consider the following attack scenario:

  1. Supposing the attacked pool is SPARTAN-WBNB. The attacker first prepares some LP tokens (WBNB-SPP) by adding liquidity to the pool.
  2. The attacker then swaps a large number of WBNB to SPARTAN, which increases the pool's baseAmount. He could split his trade into small amounts to reduce slip-based fees.
  3. The attacker now wants to increase his weight in the DaoVault. He adds his LP tokens to the pool by calling the deposit function of Dao.
  4. Dao then calls depositLP of DaoVault, causing the attacker's weight to be recalculated. Due to the large proportion of SPARTAN in the pool, the attacker's weight is artificially increased.
  5. With a higher member weight, the attacker can, for example, vote the current proposal with more votes than he should have or obtain more rewards when calling harvest of the Dao contract.
  6. The attacker then swaps back SPARTAN to WBNB and only loses the slip-based fees.

Referenced code: Utils.sol#L46-L50 Utils.sol#L70-L77 DaoVault.sol#L44-L56 Dao.sol#L201 Dao.sol#L570

A possible mitigation is to record the current timestamp when a user's weight in the DaoVault or BondVault is recalculated and force the new weight to take effect only after a certain period, e.g., a block time. This would prevent the attacker from launching the attack since there is typically no guarantee that he could arbitrage the WBNB back in the next block.

#0 - SamusElderg

2021-07-26T03:53:56Z

Recommended mitigation has been included in contributors ongoing discussions to make this more resistant to manipulation

#1 - ghoul-sol

2021-08-08T22:41:05Z

Keeping high risk because of impact

Findings Information

🌟 Selected for report: hickuphh3

Also found by: 0xRajeev, gpersoon, shw

Labels

bug
duplicate
2 (Med Risk)

Awards

215.5292 USDC - $215.53

External Links

Handle

shw

Vulnerability details

Impact

The functions of creating new DAO proposals (e.g., newActionProposal) are permissionless. Anyone can create a new proposal by paying some fees in SPARTA, as long as the previous proposal is closed. Thus, an attacker could then front-run proposals of benign users to prevent their proposals from being created. Moreover, the only way to close the malicious proposal without completing it is to wait for it to expire (15 days long). The attacker only needs to front-run benign DAO proposals every 15 days to effectively perform a DoS attack on the DAO.

Proof of Concept

Referenced code: Dao.sol#L310 Dao.sol#L319 Dao.sol#L329 Dao.sol#L339 Dao.sol#L352 Dao.sol#L407

Add a function that allows the deployer to remove malicious proposals directly in case this attack happens.

#0 - SamusElderg

2021-07-25T11:36:30Z

Duplicate of #43

Findings Information

🌟 Selected for report: cmichel

Also found by: 0xRajeev, 0xsanson, gpersoon, hickuphh3, shw

Labels

bug
duplicate
1 (Low Risk)

Awards

116.3857 USDC - $116.39

External Links

Handle

shw

Vulnerability details

Impact

Some critical parameters (e.g., the maximum number of pools that can be curated) of the Spartan protocol are hard-coded in the contracts and thus unchangeable after the contracts are deployed. Allowing admins to adjust such parameters dynamically makes the code easier to maintain and adds flexibility to the protocol without re-deploying the related contracts.

Proof of Concept

Referenced code: Dao.sol#L407 DaoVault.sol#L69 poolFactory.sol#L31

Consider adding a function with the onlyDAO modifier to allow the deployer or DAO to change these parameters in case of need.

#0 - SamusElderg

2021-07-25T11:54:50Z

Duplicate of #172

Findings Information

🌟 Selected for report: shw

Also found by: 0xRajeev

Labels

bug
2 (Med Risk)
sponsor confirmed
disagree with severity

Awards

532.1708 USDC - $532.17

External Links

Handle

shw

Vulnerability details

Impact

The claimAllForMember function of Dao is permissionless, allowing anyone to claim the unlocked bonded LP tokens for any member. However, claiming a member's LP tokens could decrease the member's weight in the BondVault, thus affecting the member's votes and rewards in the Dao contract.

Proof of Concept

For example, an attacker can intentionally front-run a victim's voteProposal call to decrease the victim's vote weight to prevent the proposal from being finalized:

  1. Supposing the victim's member weight in the BondVault is 201, the total weight is 300. The victim has some LP tokens claimable from the vault, and if claimed, the victim's weight will be decreased to 101. To simplify the situation, assuming that the victim's weight in the DaoVault and the total weight of the DaoVault are both 0.
  2. The victim wants to vote on the current proposal, which requires the majority consensus. If the victim calls voteProposal, the proposal will be finalized since the victim has the majority weight (201/300 > 66.6%).
  3. An attacker does not want the proposal to be finalized, so he calls claimAllForMember with the victim as the parameter to intentionally decrease the victim's weight.
  4. As a result, the victim's weight is decreased to 101, and the total weight is decreased to 200. The victim cannot finalize the proposal since he has no majority anymore (101/200 < 66.6%).

Similarly, an attacker can front-run a victim's harvest call to intentionally decrease the victim's reward since the amount of reward is calculated based on the victim's current weight.

Referenced code: Dao.sol#L179-L206 Dao.sol#L276-L285 Dao.sol#L369-L383 Dao.sol#L568-L574 Dao.sol#L577-L586 BondVault.sol#L104-L117 BondVault.sol#L120-L129 BondVault.sol#L155-L162

Consider removing the member parameter in the claimAllForMember function and replace all member to msg.sender to allow only the user himself to claim unlocked bonded LP tokens.

#0 - verifyfirst

2021-07-26T00:50:27Z

Although a low risk issue, it is valid and the suggested mitigation is correctly proposed.

#1 - ghoul-sol

2021-08-08T21:28:35Z

Making this medium risk as no funds are lost.

Findings Information

🌟 Selected for report: hickuphh3

Also found by: JMukesh, cmichel, shw

Labels

bug
duplicate
1 (Low Risk)

Awards

71.8431 USDC - $71.84

External Links

Handle

shw

Vulnerability details

Impact

Unbounded loops, for example, iterating over a variable-length array, could cost an arbitrarily amount of gas. If the required gas of executing the loop exceeds the block gas limit, the transactions will revert, causing some critical functions to be disabled.

Proof of Concept

For example, the claimAllForMember function of Dao iterates the listedBondAssets to claim all unlocked bonded LP tokens of a member. However, the listedBondAssets array contains the historical array of all past bond listed assets, whose length continuously increases over time. Thus, if the array length is too large, this function could cost gas more than the block gas limit. Even not, users calling this function could suffer from large gas consumption. See the following links for all unbounded loops.

Referenced code: poolFactory.sol#L100-L104 synthVault.sol#L122-L128 Dao.sol#L278-L283

Consider allowing users to decide the start and end indices when iterating the loop to avoid iterating the whole array. For example, re-write the claimAllForMember as follows:

function claimAllForMember(address member, uint start, uint end) external returns (bool) {
    for (uint i = start; i < end; i++) {
        uint claimA = calcClaimBondedLP(member, listedBondAssets[i]); // Check user's unlocked Bonded LPs for each asset
        if (claimA > 0) {
           _BONDVAULT.claimForMember(listedBondAssets[i], member); // Claim LPs if any unlocked
        }
    }
    return true;
}

#0 - SamusElderg

2021-07-25T12:02:36Z

Duplicate of #37

#1 - ghoul-sol

2021-08-08T19:54:41Z

Duplicate of #37 so low risk

Findings Information

🌟 Selected for report: gpersoon

Also found by: cmichel, shw

Labels

bug
duplicate
1 (Low Risk)

Awards

106.4342 USDC - $106.43

External Links

Handle

shw

Vulnerability details

Impact

The _handleTransferIn function of PoolFactory does not correctly handle the case where the provided parameter _token is address(0), causing the createPoolADD function to revert when the token is provided as BNB.

Proof of Concept

Referenced code: poolFactory.sol#L109-L115 Router.sol#L197-L211

Change the _handleTransferIn implementation of PoolFactory to that of Router.

#0 - SamusElderg

2021-07-25T11:52:42Z

Duplicate of #7

Findings Information

🌟 Selected for report: shw

Labels

bug
1 (Low Risk)
sponsor confirmed

Awards

394.2006 USDC - $394.20

External Links

Handle

shw

Vulnerability details

Impact

Several functions in Utils do not handle edge cases where the divisor is 0, caused mainly by no liquidity in the pool. In such cases, the transactions revert without returning a proper error message.

Proof of Concept

Referenced code: Utils.sol#L75 Utils.sol#L90 Utils.sol#L109-L110 Utils.sol#L123-L124 Utils.sol#L131 Utils.sol#L138 Utils.sol#L155 Utils.sol#L189 Utils.sol#L195 Utils.sol#L215

Check if the divisors are 0 in the above functions to handle edge cases.

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