- Published on
#9 Solo Review: Pluc
- Authors
- Name
- Beirao
- @0xBeirao
Introduction
A time-boxed security review of the Pluc protocol was done by Beirao, with a focus on the security aspects of the application's smart contracts implementation.
Disclaimer
A smart contract security review can never verify the complete absence of vulnerabilities. This is a time, resource and expertise bound effort where I try to find as many vulnerabilities as possible. I can not guarantee 100% security after the review or even if the review will find any problems with your smart contracts. Subsequent security reviews, bug bounty programs and on-chain monitoring are strongly recommended.
About Beirao
I’m an independent smart contract security researcher. I extend my skills as a contractor specializing in EVM smart contracts security. If you're in need of robust code review, I'm here to help. We can get in touch via Twitter or Email.
About Pluc
Pluc is a stable coin that uses RWA as collateral. It is an Ethos fork, which is itself a Liquity fork.
Observations
This audit focuses on changes between the Pluc audit commit and Ethos V2.1.
Mofications :
ActivePool.sol
- Remove rehypothecationPriceFeed.sol
-RewarderManager.sol
- New hook system.TroveManager.sol
- Add of all hook calls.
Privileged Roles & Actors
Severity classification
Severity | Impact: High | Impact: Medium | Impact: Low |
---|---|---|---|
Likelihood: High | High | High | Medium |
Likelihood: Medium | High | Medium | Low |
Likelihood: Low | Medium | Low | Low |
Impact - the technical, economic and reputation damage of a successful attack
Likelihood - the chance that a particular vulnerability gets discovered and exploited
Severity - the overall criticality of the risk
Security Assessment Summary
review commit hash
- Pluc Stablecoin (92ff3554)
- Vault V2 (29b8b3f2)
- Pluc Vault (49f610c7)
Deployment chains
- Polygon zkEVM
Scope
The following smart contracts were in scope of the audit: (total : xxx SLoC)
Pluc Stablecoin (92ff3554)
ActivePool.sol
PriceFeed.sol
RewarderManager.sol
TroveManager.sol
Vault V2 (29b8b3f2)
ReaperVaultERC4626.sol
ReaperVaultV2.sol
Pluc Vault (49f610c7)
IdleStrategy.sol
Vault.sol
Findings Summary
Summary :
- 2 Highs
- 1 Medium
- 3 Improvements
ID | Title | Status |
---|---|---|
[H-01] | Incompatibility between Chronicle Price Feed Adapter and Current PriceFeed Implementation | - |
[H-02] | Absence of Borrowing Interest Rate Leads to Stablecoin Depegging and Excessive Redemptions | - |
[M-01] | Hooks Make The System Vulnerable To Reentrancy Attacks. | - |
Improvements
- You can remove all
pragma experimental ABIEncoderV2;
in ^0.8.0 - Remove
safeMath
and replace it with raw operations. - Hooks don't know what the underlying operation is. For example,
onDebtDecrease
doesn't know if the decrease in debt is caused by a liquidation, a redemption, or just a trove adjustment by the user. If you think the rewarders might need this feature, you can add a variable that specifies the operation performed.
Detailed Findings
[H-01] Incompatibility between Chronicle Price Feed Adapter and Current PriceFeed Implementation
Description
The plan is to utilize Chronicle as the primary oracle in the pluc stablecoin system. Given that the initial Liquity design employs Chainlink, an adapter was proposed (here). However, the main issue is that this adapter does not handle the roundId, which is essential for the current PriceFeed
contract.
Upon examination of the adapter, it appears that the roundId is hardcoded to 1. Furthermore, the adapter does not implement the getRoundData() function. This function is crucial in the PriceFeed contract for obtaining the previous Chainlink response in _getPrevChainlinkResponse().
This basically results in a DOS of the price feed since _getPrevChainlinkResponse() will not work.
Recommendations
Therefore, it seems necessary to modify the price feed logic to some extent. Considering the current implementation, it would be advisable to update the logic and adapt it to the Chronicle implementation.
[H-02] Absence of Borrowing Interest Rate Leads to Stablecoin Depegging and Excessive Redemptions
Description
The current design of the stablecoin system does not incorporate a borrowing interest rate, which can lead to depegging and excessive redemptions under certain market conditions. As discussed, setting the management fee to 0% would not necessarily cause the plUSD to depeg due to redemptions, but it would likely result in a high volume of redemptions.
In the current high-yield market, users are incentivized to borrow plUSD at 0% interest and invest in high-yield products like sDAI. This results in constant selling pressure on plUSD, leading to frequent redemptions. To avoid redemptions, users currently need to maintain a high collateralization ratio , which is suboptimal.
Recommendations
To mitigate this issue, it is recommended to introduce a borrowing interest rate. There are three potential solutions for implementing this:
- Governance-decided rate: Similar to Maker, the governance could decide on the interest rate.
- PID controller: A PID (Proportional-Integral-Derivative) controller could be used to dynamically adjust the interest rate based on market conditions.
- Liquity v2 solution: Users could be allowed to decide the borrowing rate, and redemptions could be based on this borrowing rate instead of the collateral ratio.
By implementing one of these solutions, the stablecoin system could reduce the risk of depegging and excessive redemptions, while also improving the overall efficiency of the system.
[M-01] Hooks Make The System Vulnerable To Reentrancy Attacks.
Description
The new hook mechanism disrupts the check-effect-interaction pattern, introducing a vulnerability. If a rewarder implementation reenters the main system, it can lead to unexpected behaviors.
The threat may come from the admin, who might be unaware of the reentrancy risks, or from a compromised key. This vulnerability falls under the category of "centralization risk".
Recommendations
To mitigate this risk, it is recommended to implement a reentrancy guard on all external functions. This would prevent a function from being called again before it has completed its execution.