Platform: Code4rena
Start Date: 06/01/2022
Pot Size: $60,000 USDC
Total HM: 20
Participants: 33
Period: 7 days
Judge: LSDan
Total Solo HM: 9
Id: 67
League: ETH
Rank: 11/33
Findings: 4
Award: $2,050.02
🌟 Selected for report: 2
🚀 Solo Findings: 0
palina
The exchange performed in NonUSTStrategy.sol via Curve is executed with "0" as the minimum amount received as the result of the operation, which is likely to be exploited by front-running and may lead to the loss of funds.
_swapUnderlyingToUst(): https://github.com/code-423n4/2022-01-sandclock/blob/a90ad3824955327597be00bb0bd183a9c228a4fb/sandclock/contracts/strategy/NonUSTStrategy.sol#L74
_swapUstToUnderlying(): https://github.com/code-423n4/2022-01-sandclock/blob/a90ad3824955327597be00bb0bd183a9c228a4fb/sandclock/contracts/strategy/NonUSTStrategy.sol#L94
Manual Analysis
Set slippage protection parameter when exchanging tokens via Curve (e.g., calculate the minimum acceptable amount received using the knwon exchange rate via e.g., get_dy_underlying()).
#0 - naps62
2022-01-11T18:40:31Z
duplicate of #8
#1 - dmvt
2022-01-27T11:46:55Z
duplicate of #7
palina
In Vault.sol, totalUnderlying() (and, therefore, totalUnderlyingMinusSponsored()) include both funds available in the Vault as well as those invested in the Strategy. The calculation of amounts returned to depositors and sponsors (in _withdraw() and _unsponsor()) also relies on the values produced by totalUnderlying()/minusSponsored() which include invested funds that are not present in the Vault contract.
Therefore, even if the following check in _unsponsor() succeeds:
require(sponsorToTransfer <= totalUnderlying(), "Vault: not enough funds to unsponsor");
That is further complicated by the fact that the Vault does not implement any ability to pull funds from the Strategy. ## Proof of Concept Vault.totalUnderlying(): https://github.com/code-423n4/2022-01-sandclock/blob/a90ad3824955327597be00bb0bd183a9c228a4fb/sandclock/contracts/Vault.sol#L125 Vault.totalUnderlyingMinusSponsored(): https://github.com/code-423n4/2022-01-sandclock/blob/a90ad3824955327597be00bb0bd183a9c228a4fb/sandclock/contracts/Vault.sol#L292 _unsponsor(): https://github.com/code-423n4/2022-01-sandclock/blob/a90ad3824955327597be00bb0bd183a9c228a4fb/sandclock/contracts/Vault.sol#L394 _withdraw(): https://github.com/code-423n4/2022-01-sandclock/blob/a90ad3824955327597be00bb0bd183a9c228a4fb/sandclock/contracts/Vault.sol#L344 ## Tools Used Manual Analysis ## Recommended Mitigation Steps During withdraw()/unsponsor() check if the amount to be withdrawn is <= the amount of underlying *in possession of the Vault*. Consider adding functionality that allows pulling *some* funds out of the strategy, when necessary.
#0 - r2moon
2022-01-11T16:18:00Z
🌟 Selected for report: jayjonah8
Also found by: bugwriter001, camden, palina, sirhashalot
palina
Both _mint() and _safeMint() functions are used to mint ERC721 NFTs in Claimers and Depositors, respectively. The usage of the _mint() is, however, discouraged by the used ERC721 implementation (see PoC section), in favor of its safe counterpart.
Manual Analysis
Consider using _safeMint() in Claimers.mint().
#0 - r2moon
2022-01-11T16:25:20Z
🌟 Selected for report: palina
590.4972 USDC - $590.50
palina
BaseStrategy.sol contract implement modifiers (onlyVault() and restricted()) suggesting that its Vault should have the capability to invoke some of the restricted functionality, including withdrawAllToVault() and withdrawToVault().
However, according to the implementation, Vault.sol, contains only the call to Strategy's doHardWork() and investedAssets() (but not withdrawAllToVault() and withdrawToVault()). This suggests that this functionality might be missing in the implementation of Vault, which might lead to funds being locked in the strategy.
In BaseStrategy.sol, the defined onlyVault() modifier is never used.
Manual Analysis
Add the missing functionality (i.e., withdrawal from Strategy) to Vault or refine the modifiers in Strategy.sol to reflect the chosen role hierarchy (i.e., define the onlyGovernance() modifier to be applied to functions that are not intended to be called by a Vault; remove the onlyVault() modifier that is never used).
#0 - naps62
2022-01-18T13:39:57Z
This was indeed an issue, but a low-risk one. The implementation works as intended in this regard, but became confusing after some refactors.
#1 - dmvt
2022-01-28T13:57:50Z
Agreed on severity.
palina
There are many TODO comments left throughout the protocol code, which is confusing since it is unclear whether the functionality is present or yet to be implemented.
Manual Analysis
Remove TODO comments and/or implement the mentioned functionality.
#0 - naps62
2022-01-11T18:51:46Z
duplicate of #25
#1 - dmvt
2022-01-28T15:23:44Z
duplicate of #96
🌟 Selected for report: palina
590.4972 USDC - $590.50
palina
(1) Claimers.sol, Depositors.sol, BaseStrategy.sol miss zero-address validation on the address of the Vault set in the constructor.
Slither
Consider adding zero-address checks for important addresses like vault, treasury, etc.
#0 - naps62
2022-02-16T14:17:07Z
Claimers and Depositors are deployed by the Vault itself, which already performs these checks. The BaseStrategy ones were now fixed as well
159.4342 USDC - $159.43
palina
Some of the error messages specified in require() are not informative (e.g., "Vault: token id is not a withdraw", "insufficient"). In Depositors, the error message points to the Claimers
instead of Depositors
.
Manual Analysis
Make sure that error messages are informative and factual.
#0 - naps62
2022-01-11T18:50:50Z
The messages were kept short for optimization reasons. They are enough for the purposes they serve, and should not appear under regular use of the contracts
#1 - dmvt
2022-01-29T13:09:56Z
duplicate of #72
palina
While all other contracts use floating solidity ^0.8.10, Depositors.sol uses a strict solidity = 0.8.10, which might lead to contracts being deployed and tested with different versions. Note that the usage of floating pragma is discouraged (https://swcregistry.io/docs/SWC-103).
Manual Analysis, Slither
Consider unifying the Solidity version used throughout the protocol and locking the pragma.
#0 - naps62
2022-01-11T18:41:05Z
duplicate of #1