Hyacinth Periodical #2: Radiant Capital Exploit Deep Dive

vers La Lune
Hyacinth Friday Periodical
4 min readFeb 2, 2024

Happy Friday readers! In the first edition of our Friday Periodical we took a look at recent exploits in crypto over the past month. Notably, there were two major hacks that were both based on a precision error in the Compound/Aave v2 codebase. In this week’s periodical, let’s take a closer look at exactly what the problem is.

At its core, the issue exploits a time window when a new pool is activated in a lending market. The exploitation also relies on a known rounding issue in the current Compound/Aave codebase. It’s a now common exploit with a known solution; when a new market is created, make sure it’s activated with the amountScaled coefficient set to 0%.

The Exploit

The root cause of the exploit is the loss of precision during smart contract operations. That is to say, the error from calculation was magnified due to rounding. As an example, if I told you to divide 7/3 you would get 2.33333… since computers use base 10 and not fractions, this value would be truncated or rounded, at which point you lose precision. To get to the bottom of how this affected the contract, we must first analyze one of the attack transactions executed by the exploiter.

As discussed previously, Peckshield reported that the attacker exploited a time window after the creation of a new market in a lending market, forked from AAVE or Compound. Six seconds after deployment, they exploited a new USDC market created on Arbitrum.

When the token was initially deployed, 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. The attacker was the first individual to supply liquidity 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.

The liquidityIndex was shifted to a much 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. The rayDiv function is shown below. For context, a ray is a denary (base 10) number with 27 decimals of precision.

/**
* @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;
}

As a result of this inflation caused by the rounding, the cumulative precision loss was magnified, allowing the attacker to take away their share of profit through repeated deposit and withdrawal operations. In the blocks below, you can see how the flawed rayDiv function propagates through the mint and burn functions respectively.

/**
* @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);
}

Solution

First and foremost, this problem can be avoided by comprehensively enhancing protocols’ security. Projects can do this by reviewing their smart contracts while giving explicit attention to lending, flash loans and the management of critical variables. A top solo auditor contracted through a platform such as Hyacinth Audits can and will put the entirety of their effort into decreasing the likelihood of these attacks in the future.

Additionally, projects need to adopt advanced protocols to ensure numerical precision and secure users’ assets by strictly limiting numerical operations and controlling key variables in lending algorithms. Precision in calculations, crucial for ratios and percentages, requires using larger numerators and prioritizing multiplication over division to avoid precision loss. It’s also advised to temporarily increase precision during calculations before reverting back.

Due to Solidity’s lack of floating-point support, developers use fixed-point arithmetic, scaling values (e.g., multiplying Ether by 10¹⁸) for operations, then scaling back. This preserves precision but requires careful scaling management, highlighting fixed-point arithmetic’s essential role in maintaining accuracy safely.

Conclusion

Thanks for reading and we hope you enjoyed our deep dive into an exploit that has been plaguing crypto over the last several months. If you are building something and would like to prevent similar hacks, or any exploit, from harming to your project, please reach out to Hyacinth Audits at egansman@hyacinthaudits.xyz or post your own bounty so we can match you with the best auditor for your project!

--

--