Spartan Protocol contest - 0xRajeev'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: 3/18

Findings: 9

Award: $8,698.83

🌟 Selected for report: 14

πŸš€ Solo Findings: 0

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

0xRajeev

Vulnerability details

Impact

decreaseAllowance or approve() to lower the allowance will fail silently, i.e. not work as expected, if previous approval has set allowance to type(uint256).max. While unsafe, it is common to request/give max approval to contracts. However, in case of contract vulnerabilities or attempted rug-pulls, it is also common for users to decrease the approval to 0.

The current logic prevents the above decreaseAllowance/approval flow because it does not allow resetting allowance if current allowance is type(uint256).max. It assumes that approval is always trying to increase allowance (as noted in the code comment β€œ/ No need to re-approve if already max”) and there is nothing to do given current allowance is already uint256.max. This incorrect conditional optimization puts user funds at risk for all Pools, Synths and SPARTA tokens.

Proof of Concept

https://github.com/code-423n4/2021-07-spartan/blob/e2555aab44d9760fdd640df9095b7235b70f035e/contracts/Pool.sol#L99-L102

https://github.com/code-423n4/2021-07-spartan/blob/e2555aab44d9760fdd640df9095b7235b70f035e/contracts/Pool.sol#L92

https://github.com/code-423n4/2021-07-spartan/blob/e2555aab44d9760fdd640df9095b7235b70f035e/contracts/Synth.sol#L93-L96

https://github.com/code-423n4/2021-07-spartan/blob/e2555aab44d9760fdd640df9095b7235b70f035e/contracts/Synth.sol#L83-L88

https://github.com/code-423n4/2021-07-spartan/blob/e2555aab44d9760fdd640df9095b7235b70f035e/contracts/outside-scope/Sparta.sol#L94-L97

https://github.com/code-423n4/2021-07-spartan/blob/e2555aab44d9760fdd640df9095b7235b70f035e/contracts/outside-scope/Sparta.sol#L84-L89

Typical _approve(): https://github.com/OpenZeppelin/openzeppelin-contracts/blob/3935b907d40c9a23b04b721c2f61758df1caf722/contracts/token/ERC20/ERC20.sol#L303-L313

Tools Used

Manual Analysis

Remove conditional optimization to allow reduction of allowance even if it is currently at uint256.max.

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

0xRajeev

Vulnerability details

Impact

It is good to add a require() statement that checks the return value of arbitrary token transfers or to use something likeΒ OpenZeppelin’s safeTransfer/safeTransferFrom unless one is sure the given token returns a boolean or reverts in case of a failure. Failure to do so will cause silent failures of transfers and affect token accounting in contracts. None of the contracts use the safe versions. This should at least be used with user-specified external tokens.

Rationale for severity: Reference this similar medium-severity finding from Consensys Diligence Audit of Fei Protocol: https://consensys.net/diligence/audits/2021/01/fei-protocol/#unchecked-return-value-for-iweth-transfer-call

Proof of Concept

https://github.com/code-423n4/2021-07-spartan/blob/e2555aab44d9760fdd640df9095b7235b70f035e/contracts/Pool.sol#L199

https://github.com/code-423n4/2021-07-spartan/blob/e2555aab44d9760fdd640df9095b7235b70f035e/contracts/Pool.sol#L224

https://github.com/code-423n4/2021-07-spartan/blob/e2555aab44d9760fdd640df9095b7235b70f035e/contracts/Router.sol#L67

https://github.com/code-423n4/2021-07-spartan/blob/e2555aab44d9760fdd640df9095b7235b70f035e/contracts/Router.sol#L126

https://github.com/code-423n4/2021-07-spartan/blob/e2555aab44d9760fdd640df9095b7235b70f035e/contracts/Router.sol#L207

https://github.com/code-423n4/2021-07-spartan/blob/e2555aab44d9760fdd640df9095b7235b70f035e/contracts/Router.sol#L221

Tools Used

Manual Analysis

Add require()s or better use the safe* versions from OpenZeppelin.

#0 - SamusElderg

2021-07-22T03:09:23Z

Duplicate of #8

Findings Information

🌟 Selected for report: 0xRajeev

Also found by: cmichel

Labels

bug
3 (High Risk)
sponsor confirmed

Awards

1773.9026 USDC - $1,773.90

External Links

Handle

0xRajeev

Vulnerability details

Impact

When a member calls removeLiquiditySingle() requesting only SPARTA in return, i.e. toBASE = true, the LP tokens are transferred to the Pool to withdraw the constituent SPARTA and TOKENs back to the Router. The withdrawn TOKENs are then transferred back to the Pool to convert to SPARTA and directly transferred to the member from the Pool. However, the member’s SPARTA are left behind in the Router instead of being returned along with converted SPARTA from the Pool.

In other words, the _member's BASE SPARTA tokens that were removed from the Pool along with the TOKENs are never sent back to the _member because the _token's transferred to the Pool are converted to SPARTA and only those are sent back to member directly from the Pool via swapTo().

This effectively results in member losing the SPARTA component of their Pool LP tokens which get left behind in the Router and are possibly claimed by future transactions that remove SPARTA from Router.

Proof of Concept

LPs sent to Pool: https://github.com/code-423n4/2021-07-spartan/blob/e2555aab44d9760fdd640df9095b7235b70f035e/contracts/Router.sol#L121

SPARTA and TOKENs withdrawn from Pool to Router: https://github.com/code-423n4/2021-07-spartan/blob/e2555aab44d9760fdd640df9095b7235b70f035e/contracts/Router.sol#L122

TOKENs from Router sent to Pool: https://github.com/code-423n4/2021-07-spartan/blob/e2555aab44d9760fdd640df9095b7235b70f035e/contracts/Router.sol#L126

TOKENs in Pool converted to BASE SPARTA and sent to member directly from the Pool: https://github.com/code-423n4/2021-07-spartan/blob/e2555aab44d9760fdd640df9095b7235b70f035e/contracts/Router.sol#L127

Tools Used

Manual Analysis

  1. BASE SPARTA should also be transferred to the Pool before swapTo() so they get sent to the member along with the converted TOKENs via swapTo()
  2. Use swap(BASE) instead of swapTo() so that TOKENs are swapped for BASE SPARTA in Pool and sent back to ROUTER. Then send all the SPARTA from ROUTER to member.

#0 - verifyfirst

2021-07-22T01:56:31Z

This bug was missed in a last minute edit before pushing to code423n4, wouldn't have made it past testNet testing. However, it is a good find.

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

0xRajeev

Vulnerability details

Impact

There is no input validation of any kind (zero/sanity/threshold) on any of the proposal parameters such as typeStr, param, proposedAddress, recipient and amount to see if they make sense in the context of what is supported by the DAO.

Proposal submitters lose their proposal fee at the cost of invalid proposals. Zero address/value checks are postponed to the last stage of completion (after voting and finalization) which wastes time, gas and might make voters not vote at all leading to dangling proposals waiting for cancellation period of 15 days.

Proof of Concept

https://github.com/code-423n4/2021-07-spartan/blob/e2555aab44d9760fdd640df9095b7235b70f035e/contracts/Dao.sol#L313

https://github.com/code-423n4/2021-07-spartan/blob/e2555aab44d9760fdd640df9095b7235b70f035e/contracts/Dao.sol#L322-L323

https://github.com/code-423n4/2021-07-spartan/blob/e2555aab44d9760fdd640df9095b7235b70f035e/contracts/Dao.sol#L332-L333

https://github.com/code-423n4/2021-07-spartan/blob/e2555aab44d9760fdd640df9095b7235b70f035e/contracts/Dao.sol#L344-L345

https://github.com/code-423n4/2021-07-spartan/blob/e2555aab44d9760fdd640df9095b7235b70f035e/contracts/Dao.sol#L454

https://github.com/code-423n4/2021-07-spartan/blob/e2555aab44d9760fdd640df9095b7235b70f035e/contracts/Dao.sol#L464

https://github.com/code-423n4/2021-07-spartan/blob/e2555aab44d9760fdd640df9095b7235b70f035e/contracts/Dao.sol#L472

https://github.com/code-423n4/2021-07-spartan/blob/e2555aab44d9760fdd640df9095b7235b70f035e/contracts/Dao.sol#L480

https://github.com/code-423n4/2021-07-spartan/blob/e2555aab44d9760fdd640df9095b7235b70f035e/contracts/Dao.sol#L494

https://github.com/code-423n4/2021-07-spartan/blob/e2555aab44d9760fdd640df9095b7235b70f035e/contracts/Dao.sol#L511-L512

Tools Used

Manual Analysis

Perform relevant validation of input parameters of new proposals to reject any parameters that are clearly irrelevant/incorrect e.g. unsupported typeStr, zero address, zero param, zero grant amount, listing/delisting bonds that are already in the state being proposed, adding/removing curated pools that are already in the state being proposed. These should be performed in the new*Proposal() functions instead of the proposal action functions which get executed after a successful vote. Input validation should happen closest to the interface where inputs are received instead of being postponed to after voting/finalisation.

#0 - SamusElderg

2021-07-23T06:33:27Z

Duplicate of #43

Findings Information

🌟 Selected for report: cmichel

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

Labels

bug
duplicate
2 (Med Risk)

Awards

116.3857 USDC - $116.39

External Links

Handle

0xRajeev

Vulnerability details

Impact

Unnecessary/incorrect access control modifier is typically an indication of missing critical authorization checks. The onlyDAO modifier (used in various protocol contracts) is present in synthFactory.sol but used only in the purgeDeployer (which sets deployer to 0 address) and is also incorrect in that it only checks against DEPLOYER and not the DAO address. This is effectively unnecessary.

Proof of Concept

https://github.com/code-423n4/2021-07-spartan/blob/e2555aab44d9760fdd640df9095b7235b70f035e/contracts/synthFactory.sol#L21-L25

https://github.com/code-423n4/2021-07-spartan/blob/e2555aab44d9760fdd640df9095b7235b70f035e/contracts/synthFactory.sol#L31-L34

Tools Used

Manual Analysis

Re-evaluate access control logic in modifier and purgeDeployer() to either use this modifier for other functions or else remove it along with purgeDeployer().

#0 - SamusElderg

2021-07-23T08:31:43Z

Duplicate of #172

#1 - ghoul-sol

2021-08-08T19:24:52Z

Duplicate of #172

Findings Information

🌟 Selected for report: shw

Also found by: 0xRajeev

Labels

bug
duplicate
2 (Med Risk)

Awards

532.1708 USDC - $532.17

External Links

Handle

0xRajeev

Vulnerability details

Impact

The harvest() function operates on msg.sender who may not be the same as member used in depositLPForMember(). A griefing attacker (msg.sender) can target an arbitrary member (with existing weight) by depositing some dust LP which gets accounted towards member's deposits, potentially accumulates harvest rewards (in the harvest function) and then forces reset of member's last harvest time on L166 which prevents the actual member from accumulating harvest rewards for that duration when member calls deposit() or depositLPForMember() directly. The member thus loses harvest rewards/incentives for that duration because of the lastTime reset.

This is a different vector compared to the other similar finding where using a relayer leads to member missing harvest rewards, because this vector does not depend on the use of a relayer at all.

Also, this is a griefing attack where the attacker potentially does not get any benefits/rewards unless it is another participating member in the protocol.

Proof of Concept

https://github.com/code-423n4/2021-07-spartan/blob/e2555aab44d9760fdd640df9095b7235b70f035e/contracts/Dao.sol#L154-L168

https://github.com/code-423n4/2021-07-spartan/blob/e2555aab44d9760fdd640df9095b7235b70f035e/contracts/Dao.sol#L178-L189

Tools Used

Manual Analysis

Pass an address parameter to harvest() and use that instead of msg.sender

#0 - SamusElderg

2021-07-26T02:10:48Z

Duplicate of #235

Findings Information

🌟 Selected for report: hickuphh3

Also found by: 0xRajeev

Labels

bug
duplicate
2 (Med Risk)

Awards

532.1708 USDC - $532.17

External Links

Handle

0xRajeev

Vulnerability details

Impact

In claimForMember, the member claims back some of their bonded LPs. The check to see if claimRate can be made 0 should preceed the _claimable deduction on L110. This misplaced check after deduction leads to incorrect zero-ing of member’s non-zero claimable bonded LPs i.e. loss of funds. This happens whenever member claims half of their bonded LPs.

Scenario: Say bondedLP[member] = 400 before duduction and _claimable happens to be 200. Then, after L110, bondedLP[member] = 400 - 200 = 200, which satisfies the predicate on L111 because bondedLP[member] == _claimable == 200. This leads to claimRate[member] being set to 0 although member still has a remainder 200 bonded LPs yet to claim.

Proof of Concept

https://github.com/code-423n4/2021-07-spartan/blob/e2555aab44d9760fdd640df9095b7235b70f035e/contracts/BondVault.sol#L110-L113

Tools Used

Manual Analysis

Move L110 deduction to after L113. The conditional check and setting of claimRate[member] to zero should happen before the deduction.

#0 - verifyfirst

2021-07-22T01:53:58Z

This is a duplicate with #42

#1 - ghoul-sol

2021-08-08T22:06:38Z

duplicate of #42 so medium risk

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