How Was Radiant Capital Exploited?
TL;DR
On January 3, 2024, Radiant Capital was exploited on the Arbitrum Chain due to a smart contract vulnerability, which resulted in a loss of 1902 ETH, worth approximately $4.5 million.
Introduction to Radiant
Radiant is a lending and borrowing protocol.
Vulnerability Assessment
The root cause of the exploit is the loss of precision during smart contract operations.
Steps
Step 1:
We attempt to analyze one of the attack transactions executed by the exploiter.
Step 2:
The attacker is able to exploit a time window when a new market is activated in a lending market, forked from AAVE or Compound.
The issue was vested in the newly created USDC market on the Arbitrum chain, which was exploited 6 seconds after its deployment.
Step 3:
For the newly deployed token, the totalSupply parameter was uninitialized or set to 0, which allowed the hacker to perform price manipulation of the underlying assets through a flash loan attack.
Step 4:
The attacker was the first individual to supply in this new USDC market, and he then used that opportunity to manipulate the liquidityIndex, a key factor in determining the AToken user balances, to borrow all the ETH.
Step 5:
The liquidityIndex was shifted to a very larger value than what it should have been, and a flaw in the rayDiv function, a known rounding issue, was taken advantage of to siphon funds from the pool.
/**
* @dev Divides two ray, rounding half up to the nearest ray
* @param a Ray
* @param b Ray
* @return The result of a/b, in ray
**/
function rayDiv(uint256 a, uint256 b) internal pure returns (uint256) {
require(b != 0, Errors.MATH_DIVISION_BY_ZERO);
uint256 halfB = b / 2;
require(a <= (type(uint256).max - halfB) / RAY, Errors.MATH_MULTIPLICATION_OVERFLOW);
return (a * RAY + halfB) / b;
}
Step 6:
As a result of this inflation, the cumulative precision loss was magnified, allowing the attacker to take away their share of profit through repeated deposit and withdrawal operations.
/**
* @dev Mints `amount` aTokens to `user`
* - Only callable by the LendingPool, as extra state updates there need to be managed
* @param user The address receiving the minted tokens
* @param amount The amount of tokens getting minted
* @param index The new liquidity index of the reserve
* @return `true` if the the previous balance of the user was 0
*/
function mint(address user, uint256 amount, uint256 index) external override onlyLendingPool returns (bool) {
uint256 previousBalance = super.balanceOf(user);
uint256 amountScaled = amount.rayDiv(index);
require(amountScaled != 0, Errors.CT_INVALID_MINT_AMOUNT);
_mint(user, amountScaled);
emit Transfer(address(0), user, amount);
emit Mint(user, amount, index);
return previousBalance == 0;
}
/**
* @dev Burns aTokens from `user` and sends the equivalent amount of underlying to `receiverOfUnderlying`
* - Only callable by the LendingPool, as extra state updates there need to be managed
* @param user The owner of the aTokens, getting them burned
* @param receiverOfUnderlying The address that will receive the underlying
* @param amount The amount being burned
* @param index The new liquidity index of the reserve
**/
function burn(address user, address receiverOfUnderlying, uint256 amount, uint256 index) external override onlyLendingPool {
uint256 amountScaled = amount.rayDiv(index);
require(amountScaled != 0, Errors.CT_INVALID_BURN_AMOUNT);
_burn(user, amountScaled);
IERC20(_underlyingAsset).safeTransfer(receiverOfUnderlying, amount);
emit Transfer(user, address(0), amount);
emit Burn(user, receiverOfUnderlying, amount, index);
}
Aftermath
The team acknowledged the occurrence of the exploit and stated that the Radiant DAO council has temporarily paused lending and borrowing operations on the Arbitrum chain while they investigate the issue.
The team has also sent an on-chain message to the exploiter with hopes of recovering the stolen assets.
Solution
To address the Radiant Capital exploit, it is imperative to swiftly and comprehensively enhance security protocols. This process should commence with a thorough examination of the smart contracts, with particular attention given to lending, flash loans, and the management of critical variables. This scrutiny is essential not only for safeguarding the accuracy of calculations but also for deterring any potential manipulative actions that might trigger similar breaches in the future.
Simultaneously, the platform should introduce advanced precision protection protocols. These measures involve imposing strict limitations on numerical operations to prevent loss of precision and establishing rigorous controls over pivotal variables that influence lending algorithms. Such robust measures are indispensable for intercepting any suspicious transactions or activities, thereby safeguarding users’ assets against intricate threats.
The significance of precision in numerical operations cannot be overstated, especially when dealing with calculations involving ratios, rates, or percentages. This necessitates allowing for larger numerators in fractions. Additionally, careful consideration should be given to the sequence of operations, with a preference for prioritizing multiplication over division to maintain precision. A recommended practice involves temporarily elevating variables to a higher precision for all calculations and then reverting to the required precision.
Solidity’s absence of support for floating-point numbers compels developers to resort to fixed-point arithmetic for decimal numbers. This method involves scaling values, such as multiplying by 10¹⁸ for Ether, executing operations in this scaled integer form, and subsequently scaling down as necessary. While effective in preserving precision, this approach demands meticulous management of scaling factors, underscoring the pivotal role of fixed-point arithmetic in retaining precision without introducing risks.
This article was originally published by Pukar Acharya elsewhere.
Enjoy Reading This Article?
Here are some more articles you might like to read next: