Blurry Pistachio Rhino How Splitting Burns Reduces Dynamic Fees

by ADMIN 64 views
Iklan Headers

Hey guys! Today, we're diving into a fascinating vulnerability in the CAP's dynamic burn fee mechanism. It's a bit of a mouthful, but stick with me—it's worth understanding. This issue, dubbed "Blurry Pistachio Rhino," reveals how users can cleverly reduce their burn fees by splitting large burns into smaller ones. Let's break it down, shall we?

Understanding the Dynamic Burn Fee Mechanism

At the heart of this vulnerability is CAP's dynamic burn (redemption) fee mechanism. This system calculates fees based on the asset allocation ratio, which is a fancy way of saying it looks at how assets are distributed after a burn. The crucial point here is that users can exploit this mechanism to their advantage. By splitting a large burn into multiple smaller burns, users can significantly reduce their total fees. This isn't just a minor tweak; it's a fundamental flaw in the fee calculation that can lead to some serious savings for savvy users.

When a user initiates a burn, the system calculates the amountOut (the amount received) and the fee using the MinterLogic.amountOut function. Within this function, the _amountOutBeforeFee function plays a key role. It's here that the newRatio is calculated, which directly influences the fees. This formula, newRatio = (allocationValue - assetValue) * RAY_PRECISION / (capValue - assetValue), is where the magic (or rather, the exploit) happens. You see, when a user divides their total burn amount into smaller chunks, the ratio decreases slightly with each transaction, instead of a single large drop. This incremental decrease is the key to lower fees.

To really get why this matters, let's talk about how this newRatio is used. It feeds into the _applyFeeSlopes function, which is responsible for calculating the actual fee. This function uses the newRatio to determine the fee rate. By burning repeatedly in smaller amounts, users can exploit this fee structure to pay less in fees compared to someone who burns the entire amount at once. This isn't just theoretical; it has practical implications for anyone using the platform. Imagine paying significantly less simply by splitting your transaction—that's the power of understanding this vulnerability.

The Root Cause: A Deep Dive into the Math

Let's get a bit technical for a moment and dive into the root cause. The vulnerability stems from how the newRatio is calculated in the _amountOutBeforeFee function. The formula, as we mentioned, is newRatio = (allocationValue - assetValue) * RAY_PRECISION / (capValue - assetValue). It's crucial to understand how this formula behaves when the burn amount is split. When a user burns a large amount at once, the assetValue increases significantly, leading to a substantial decrease in newRatio. This decrease triggers a higher fee because the system perceives a greater imbalance in asset allocation.

However, when the same total burn amount is divided into multiple smaller burns, the assetValue increases incrementally with each burn. This means that the newRatio decreases in smaller steps, rather than one large step. Consequently, each smaller burn incurs a lower fee because the system doesn't perceive as drastic an imbalance in asset allocation. Over multiple burns, these smaller fees add up to a significantly lower total fee compared to a single large burn. It's a classic case of exploiting the nuances of a mathematical formula to gain an advantage.

This behavior is further amplified by how the newRatio is used in the _applyFeeSlopes function. This function calculates the fee based on the relationship between the newRatio, the fees.optimalRatio, and the fees.burnKinkRatio. If the newRatio is less than the fees.optimalRatio, the fee rate is calculated based on whether the newRatio is also less than the fees.burnKinkRatio. The fee rate is determined using the slopes (fees.slope0 and fees.slope1) defined in the system. The crucial point is that the fee rate increases as the newRatio decreases, but this increase is not linear. By keeping the individual decreases in newRatio small, users can avoid the steeper parts of the fee slope, resulting in lower overall fees.

The implications of this root cause are significant. It means that the dynamic fee mechanism, which is designed to balance asset allocation and prevent manipulation, can be circumvented by a simple strategy of splitting burns. This not only undermines the intended purpose of the fee mechanism but also creates an unfair advantage for users who are aware of this vulnerability. It's a critical flaw that needs to be addressed to ensure the fairness and stability of the system.

Impact: Undermining the Fee Model

The impact of this vulnerability is significant: splitting burns allows users to pay lower cumulative fees, which directly undermines the protocol’s fee model. It’s not just a minor inconvenience; it creates an unfair advantage for those who know the trick, putting standard burners at a disadvantage. This can lead to a loss of trust in the system and potentially affect its long-term viability.

To put it simply, the fee model is designed to ensure fairness and stability within the platform. By charging fees, the system aims to balance asset allocation and discourage malicious behavior. However, this vulnerability allows users to bypass the intended fee structure, effectively getting a discount on their burns. This discount isn't available to everyone, creating an uneven playing field. Users who aren't aware of this strategy end up paying more, while those in the know benefit unfairly.

This disparity can have far-reaching consequences. If a significant number of users start exploiting this vulnerability, the protocol's revenue from burn fees could decrease. This could impact the system's ability to maintain its operations, fund development efforts, or provide incentives for other participants. Moreover, the perception of unfairness can erode trust in the platform. Users might feel that the system is rigged in favor of a select few, leading to decreased participation and liquidity.

Furthermore, the vulnerability can incentivize a specific type of behavior that is detrimental to the overall health of the system. By rewarding users for splitting burns, the protocol might inadvertently encourage smaller, more frequent transactions. This can increase the load on the system, potentially leading to higher gas costs and slower transaction times for everyone. It's a classic example of unintended consequences, where a seemingly minor flaw in the fee mechanism can have a cascading effect on the entire ecosystem.

In essence, this vulnerability strikes at the heart of the protocol's economic model. It undermines the principles of fairness and transparency, creates an uneven playing field, and can potentially impact the long-term sustainability of the system. Addressing this issue is crucial to maintaining the integrity of the platform and ensuring that all users are treated equitably.

Proof of Concept (PoC): Seeing the Exploit in Action

To really drive the point home, let’s look at a Proof of Concept (PoC). This is where we show how the exploit works in practice. First, you need to update test/deploy/TestDeployer.sol to set specific values for slope0 and slope1. These slopes are crucial in the fee calculation, and by setting them, we can clearly demonstrate the fee reduction.

 FeeConfig memory fee = FeeConfig({
 minMintFee: 0.005e27, // 0.5% minimum mint fee
180@> slope0: 0.01e27, // @audit 1%
181@> slope1: 0.1e27, // @audit 10%
 mintKinkRatio: 0.85e27,
 burnKinkRatio: 0.15e27,
 optimalRatio: 0.33e27
 });

Next, we add a function in test/vault/Vault.burn.t.sol to simulate the burn exploit. This function, test_audit_burn, sets up a scenario where a user burns a large amount of tokens and then compares the fees paid when burning the entire amount at once versus splitting it into smaller burns.

 function test_audit_burn() public {
 address audit_user = makeAddr("audit_user");
 _initTestUserMintCapToken(usdVault, audit_user, 10500e18);

 vm.startPrank(audit_user);

 // burn the cUSD tokens we own
 uint256 burnAmount = 10000e18;
 uint256 minOutputAmount = 9500e6; // Expect at least 95% back accounting for potential fees
 uint256 deadline = block.timestamp + 1 hours;

 // Normal burn
 uint beforeState = vm.snapshot();
 uint256 balance_before = usdt.balanceOf(audit_user);
 uint256 outputAmount = cUSD.burn(address(usdt), burnAmount, minOutputAmount, audit_user, deadline);
 uint256 balance_after = usdt.balanceOf(audit_user);

 console.log("total", balance_after - balance_before);

 vm.revertTo(beforeState);
 balance_before = cUSD.balanceOf(audit_user);
 uint256 nSplit = 20;
 for(uint256 i = 0; i < nSplit; i ++) {
 cUSD.burn(address(usdt), burnAmount / nSplit, minOutputAmount / nSplit, audit_user, deadline);
 }
 balance_after = usdt.balanceOf(audit_user);
 console.log("split", balance_after - balance_before);
 }

When you run this test, you’ll see a clear difference in the amount received. In the PoC, burning the total amount once yields an asset return of 9,948,561,152, while splitting the burn into 20 smaller transactions results in an asset return of 9,975,064,513. This shows that splitting the burn leads to a higher return, effectively reducing the fees paid.

The log output from the test illustrates this point perfectly:

total 9948561152
split 9975064513

This means that by splitting the burn, the user receives approximately 26,503,361 more assets. This difference represents the amount of fees saved by exploiting the vulnerability. The PoC clearly demonstrates that splitting burns is a viable strategy for reducing fees, highlighting the need for a fix.

Mitigation Strategies: How to Fix the Blurry Pistachio Rhino

So, how do we fix this "Blurry Pistachio Rhino"? There are a couple of solid mitigation strategies we can consider. The first involves introducing a minimum threshold for _amountIn. This would essentially prevent very small burns from benefiting from the fee reduction exploit. By setting a minimum size for burn transactions, we can ensure that users can't game the system by splitting their burns into tiny amounts.

Another approach is to implement a time-based cooldown between burns in the vault.burn function. This means that users would have to wait a certain period before initiating another burn transaction. This cooldown would discourage frequent splitting of burns, as it would take significantly longer to burn a large amount in small increments. This strategy adds friction to the exploit, making it less attractive for users to attempt.

Both of these solutions have their merits. A minimum threshold on _amountIn directly addresses the issue of tiny burns exploiting the fee mechanism. It's a straightforward and effective way to limit the vulnerability. A time-based cooldown, on the other hand, takes a more holistic approach. It not only discourages burn splitting but also adds a layer of protection against other potential exploits that might involve frequent transactions.

Choosing the right mitigation strategy depends on the specific goals and constraints of the system. A combination of both approaches might even be the most effective solution. For instance, we could set a reasonable minimum threshold for _amountIn and also implement a short cooldown period. This would provide a multi-layered defense against fee manipulation.

Ultimately, the goal is to ensure that the fee mechanism functions as intended, providing a fair and balanced system for all users. By implementing one or more of these mitigation strategies, we can effectively address the "Blurry Pistachio Rhino" vulnerability and maintain the integrity of the platform.

Conclusion: Securing the System

In conclusion, the "Blurry Pistachio Rhino" vulnerability is a prime example of how seemingly minor flaws in a system's design can have significant consequences. By understanding the intricacies of the dynamic burn fee mechanism, we've uncovered a way for users to reduce their fees unfairly. This not only undermines the protocol’s intended fee structure but also creates an uneven playing field for users.

We’ve seen how splitting burns into smaller transactions can lead to lower cumulative fees, thanks to the way the newRatio is calculated and used in the _applyFeeSlopes function. The PoC clearly demonstrates this, showing a tangible difference in asset returns between burning a lump sum and splitting it into smaller burns. This isn't just theoretical; it's a practical exploit that can be used to gain an advantage.

To address this, we've explored mitigation strategies like introducing a minimum threshold for _amountIn and implementing a time-based cooldown between burns. These measures aim to prevent the exploitation of the fee mechanism and ensure that all users are treated equitably. The choice of which strategy to implement, or whether to combine them, depends on the specific needs and priorities of the system.

Ultimately, securing a system is an ongoing process. It requires a deep understanding of the underlying mechanisms, a keen eye for potential vulnerabilities, and a commitment to continuous improvement. By identifying and addressing issues like the "Blurry Pistachio Rhino," we can build more robust, fair, and trustworthy platforms. And that's what it's all about, isn't it? Creating systems that benefit everyone, not just those who know how to game the system. So, let's keep exploring, keep questioning, and keep building a better future, guys!