Solidity, as the cornerstone of Ethereum’s smart contract landscape, has its strengths, but it’s not without its vulnerabilities. Among these vulnerabilities is the risk of precision loss—a subtle but significant quirk that can have profound consequences for contract functionality. In this article, we will delve deeper into the intricacies of precision loss, examine real-world contract complexities, and provide techniques to mitigate its effects.
Fixed-Point Arithmetic and the EVM
Table of Contents
- 1 Fixed-Point Arithmetic and the EVM
- 2 Spotlight on Vulnerabilities
- 3 Deeper Dive: A Decentralized Exchange Rate Oracle
- 4 Exploring Precision Loss in a Solidity Staking Pool Contract
- 5 Pinpointing the Precision Loss
- 6 Real-World Implications
- 7 Mitigation Strategies In Staking Pool
- 8 Mitigating Precision Loss In General Solidity Program
- 9 The Cost of Oversight
- 10 Conclusion
The Ethereum Virtual Machine (EVM) does not natively support floating-point arithmetic, a design decision rooted in determinism and predictability. Instead of relying on floating points, developers work within the confines of fixed-point arithmetic. In this system, numbers are represented as integers, but there’s a mutual understanding of where the implied “decimal” exists.
Solidity, the primary language for Ethereum smart contracts, mirrors this limitation. In fact, compilers of Solidity version 0.4.24 and above neither support floating-point nor fixed-point data types. Floating-point numbers inherently represent decimal values as binary fractions. While this might be suitable for many computational scenarios, in the world of blockchain, it can lead to precision loss during calculations, especially when numbers of vast differences in magnitude interact.
This computational design intricacy means that the structure of smart contracts demands meticulous deliberation of numerical handling, particularly when mimicking floating-point behavior. Ignoring the subtleties can birth precision vulnerabilities. Such a loss of precision, albeit sometimes minuscule in appearance, can pose significant threats, resulting in unintended and sometimes catastrophic consequences for smart contract functionalities.
Therefore, while the EVM’s model might seem restrictive, it emphasizes the importance of deterministic outcomes in financial transactions and contractual agreements. Developers need to be acutely aware of these limitations and design their contracts with an abundance of caution, ensuring that precision is maintained and the contract behaves as intended across all scenarios.
Spotlight on Vulnerabilities
Division & Multiplication Sequence
Take, for example, a DeFi liquidity pool where the order of operations can result in substantial losses:
uint256 userBalance = 150;
uint256 totalLiquidity = 1000;
uint256 poolReward = 100;
uint256 userReward = (userBalance * poolReward) / totalLiquidity; // Results in 15, not 15.75
The discrepancy, 0.75 in this case, is due to the truncation of results, and in large-scale operations, this can accumulate significantly.
Interest Calculation
Another nuanced scenario is calculating interest in lending platforms:
uint256 principal = 1000;
uint256 rate = 5; // 5% interest rate
uint256 time = 2; // 2 periods
uint256 interest = (principal * rate * time) / 100; // Here's the catch!
Given the absence of floating-point representation, the formula’s result might not always be accurate, especially when dealing with more complex compounding interest formulas.
Deeper Dive: A Decentralized Exchange Rate Oracle
Consider a decentralized oracle determining exchange rates between tokens:
pragma solidity ^0.8.0;
contract ExchangeRateOracle {
mapping(bytes32 => uint256) rates;
function setRate(bytes32 pair, uint256 rate) public {
rates[pair] = rate;
}
function convert(bytes32 pair, uint256 amount) public view returns (uint256) {
return (amount* rates[pair]) / (10**18); // Assume 18 decimals for rate
}
}
If the rate for a pair
is 1.2345 (represented as 1234500000000000000
in the contract), converting an amount
could lead to minor discrepancies because of the truncation. Over thousands of transactions, these discrepancies might result in significant losses or gains.
Exploring Precision Loss in a Solidity Staking Pool Contract
In the domain of Ethereum smart contracts, the implications of precision loss are far-reaching, particularly in financial applications like staking pools. Here, we shall dissect a more complex Solidity staking pool contract to understand where precision loss creeps in, its implications, and the strategies to manage it.
The Technical Nuances of a Staking Contract
Smart contracts for staking pools are intended to distribute rewards based on the proportion of the user’s stake in the pool. The core functionality revolves around two primary actions: depositing stakes and claiming rewards.
pragma solidity ^0.8.0;
contract StakingPool {
mapping(address => uint256) public stakes;
uint256 public totalStakes;
uint256 public totalReward;
// Users deposit funds into the pool
function deposit(uint256 amount) public {
require(amount > 0, "Amount must be greater than 0");
stakes[msg.sender] += amount;
totalStakes += amount;
// Emit an event for the deposit (omitted for brevity)
}
// Users claim their rewards from the pool
function claimReward() public {
uint256 userStake = stakes[msg.sender];
require(userStake > 0, "No stake to claim reward");
// Calculate user's reward based on stake proportion
uint256 reward = (userStake * totalReward) / totalStakes;
// Check for precision loss
assert(reward <= totalReward);
totalReward -= reward;
// Transfer the reward to the user (functionality omitted for brevity)
// Emit an event for claiming a reward (omitted for brevity)
}
}
Pinpointing the Precision Loss
The crux of the precision issue lies in the claimReward
function:
uint256 reward = (userStake * totalReward) / totalStakes;
Here’s what happens: Solidity executes integer division on the EVM, and since the EVM lacks floating-point arithmetic, the division result truncates any remainder. The result is a whole number, devoid of any fraction, despite a real-world scenario where fractions can represent a significant value.
For example, if a user’s stake is 333 wei in a total pool of 1000 wei, and the total reward is 100 wei, the user’s expected reward would be 33.3 wei. However, due to Solidity’s integer division, the user receives only 33 wei. The 0.3 wei is effectively ‘lost’ in the transaction.
Real-World Implications
Such a loss might seem negligible in a single transaction. However, in a high-frequency trading environment or a large-scale DeFi protocol, these fractions can accumulate rapidly. Over time, the unclaimed rewards might become substantial, leading to two significant issues:
- Equity Loss: Users not receiving their full reward amount can result in perceived unfairness and loss of trust, impacting the protocol’s reputation and user retention.
- Resource Lock: The unclaimed rewards, the ‘dust’, remain locked within the contract, creating inefficiencies and, in some cases, loss of resources.
Mitigation Strategies In Staking Pool
A robust staking pool contract must incorporate strategies to mitigate precision loss:
- Reward Scaling: By scaling the reward by a factor of 10^x before the division and then scaling it down after the calculation, the contract captures the precision lost in truncation.
- Fractional Tracking: Implement a mechanism to track fractions of rewards, even if they are not immediately distributed. These can be accumulated and distributed when they add up to a whole unit.
- Use of Fixed-Point Libraries: Incorporating libraries that handle fixed-point arithmetic can provide the necessary precision, albeit with increased gas costs.
- Assertive Checks: Implement assertive checks post-calculation to ensure that the precision loss is within acceptable bounds and that the contract’s total reward balance remains consistent.
Precision loss in Solidity is not merely a technical challenge; it’s a fundamental consideration that can affect user experience, trust, and the financial integrity of a DeFi system. By understanding the mechanisms of precision loss and integrating safeguards into smart contracts, developers can create more reliable, equitable, and efficient financial products on the blockchain.
Mitigating Precision Loss In General Solidity Program
Higher Precision Internal Representation
Use a higher internal precision to represent numbers. For instance, instead of representing 1.23 as 123, represent it as 12300 or even 1230000, thus reducing the chances of truncation errors.
Rounding Helpers
Introduce rounding functions to ensure that division operations round to the nearest whole number rather than always truncating.
function divideAndRound(uint256 a, uint256 b) internal pure returns (uint256) {
return (a + (b / 2)) / b;
}
By adding half of b
to a
, this function rounds the result to the nearest whole number.
Utilizing Libraries & Tools
Several external libraries like SafeMath, ABDK, and FixedPoint provide functions and tools that can help alleviate precision-related issues. Incorporate these tested libraries into your contracts rather than reinventing the wheel.
The Cost of Oversight
While these inaccuracies might seem minor, in the world of DeFi, where vast sums are transacted, a minor error could translate into substantial financial repercussions. Moreover, trust in the decentralized ecosystem relies heavily on code accuracy and the predictability of outcomes.
Some of the recent hacks that exploited this type of vulnerability:
Onyx Protocol Hack
On November 1, 2023, the decentralized finance platform @OnyxProtocol on the Ethereum network suffered a significant setback due to a “precision loss vulnerability” exploit, leading to losses estimated at approximately $2.1 million.
This specific vulnerability is tied to a rounding issue often observed in CompoundV2 forks. The attacker cunningly initiated the exploit right after the oPEPE market was set up in the Onyx Protocol, as outlined in proposal 22.
By minting a tiny number of shares and then donating a large volume of PEPE to the oPEPE market, the malefactor managed to distort the exchange rate. This enabled the attacker to borrow Ethereum (ETH) and exploit the precision loss vulnerability in the redeemUnderlying
function, ensuring the entire PEPE amount was reverted back to them.
HopeLend Protocol Exploit
On October 18, 2023, the Ethereum-based HopeLend Protocol fell victim to an exploit, resulting in losses of around $835,000. The linchpin behind the exploit was a “Precision Loss vulnerability” inherent within Htoken’s contract.
Delving deeper into the mechanics of the attack, the malefactor capitalized on the imprecise calculations of the liquidity index during the execution of the _handleFlashLoanRepayment
function. Here’s a step-by-step breakdown of the attack:
- The attacker initiated the exploit by obtaining a FlashLoan of 2,000 WBTC.
- Subsequently, these funds were infused into the Pool contract, manipulating the reserve’s liquidity index.
- Through this maneuver, the liquidity index of hEthWBTC, originally set at 1e27, was maliciously escalated to an astonishing 7,560,000,001e27.
- The attacker then amplified their gains by borrowing assets from various markets. Owing to the precision loss, they were able to provide a lower collateral of WBTC in comparison to what would have been the genuine requirement.
These incidents serve as yet another cautionary tale, highlighting the paramount importance of precision in smart contract implementations. Even the slightest inaccuracy, especially in critical components like liquidity index calculations, can open the door to significant vulnerabilities and financial ramifications.
Conclusion
Solidity’s precision loss vulnerability underscores the need for meticulous contract design. As we inch closer to a more decentralized future, understanding and mitigating such vulnerabilities becomes not just a technical necessity but a foundational requirement for a trustable and reliable decentralized financial system.
Unfortunately, many companies remain oblivious to the vital significance of Smart Contract auditing, a crucial facet of programmable finance. To attract a substantial investor base and ensure the security of their Smart Contracts, an audit becomes indispensable. By conducting a thorough Smart Contract Audit, organizations can proactively prevent vulnerabilities and potential security breaches.
At Immunebytes, we recognize the gravity of secure smart contract development and the dire repercussions of overlooked vulnerabilities. If you seek assurance in the security and functionality of your Smart Contract, we stand ready to assist. Reach out to Immunebytes for a comprehensive Smart Contract Audit and fortify your project against unforeseen risks.