Core Contracts

Regnum Aurum Acquisition Corp
HardhatReal World AssetsNFT
77,280 USDC
View results
Submission Details
Severity: high
Invalid

Compound Interest Rate Manipulation Through Taylor Series Approximation Error

Summary

The ReserveLibrary.sol's implementation of compound interest calculation using Taylor series approximation contains a critical vulnerability that can be exploited through sandwich attacks. The use of only 7 terms in the Taylor series expansion leads to significant approximation errors that can be manipulated for profit.

Vulnerability Details

In ReserveLibrary.sol, compound interest is calculated using Taylor series approximation:

function calculateCompoundedInterest(uint256 rate,uint256 timeDelta) internal pure returns (uint256) {
if (timeDelta < 1) {
return WadRayMath.RAY;
}
uint256 ratePerSecond = rate.rayDiv(SECONDS_PER_YEAR);
// @audit-issue Potential precision loss in division
uint256 exponent = ratePerSecond.rayMul(timeDelta);
// @audit-issue Critical: Only uses 7 terms for Taylor series
// This can lead to significant errors for large exponents
return WadRayMath.rayExp(exponent);
}
function calculateUsageIndex(uint256 rate, uint256 timeDelta ,uint256 lastIndex) internal pure returns (uint128) {
// @audit-issue Compounds potentially inaccurate interest factor
uint256 interestFactor = calculateCompoundedInterest(rate, timeDelta);
// @audit-issue Propagates error through multiplication
return lastIndex.rayMul(interestFactor).toUint128();
}
function rayExp(uint256 x) internal pure returns (uint256 result) {
// Taylor series expansion for e^x to 7 terms:
// e^x = 1 + x + x^2/2! + x^3/3! + x^4/4! + x^5/5! + x^6/6! + x^7/7!
// @audit-issue Approximation becomes significantly inaccurate for large x
result = WadRayMath.RAY; // 1
if (x > 0) {
result += x; // + x
result += (x * x) / 2; // + x^2/2!
result += (x * x * x) / 6; // + x^3/3!
result += (x * x * x * x) / 24; // + x^4/4!
result += (x * x * x * x * x) / 120; // + x^5/5!
result += (x * x * x * x * x * x) / 720; // + x^6/6!
result += (x * x * x * x * x * x * x) / 5040; // + x^7/7!
}
}

The key issues are:

  1. The Taylor series expansion uses only 7 terms, leading to significant approximation errors for large exponents

  2. No bounds checking on input values that could cause large approximation errors

  3. Error propagation through subsequent calculations

  4. No protection against sandwich attacks exploiting these errors

Impact

An attacker can:

  1. Manipulate interest rates through carefully timed sandwich attacks

  2. Exploit approximation errors for profit

  3. Disrupt the protocol's interest rate mechanism

  4. Cause losses for other users

Proof of Concept (PoC)

This PoC demonstrates how the Taylor series approximation error can be exploited in a sandwich attack:

const { expect } = require("chai");
const { ethers } = require("hardhat");
const { mine, time } = require("@nomicfoundation/hardhat-network-helpers");
describe("Compound Interest Rate Manipulation", function() {
let lendingPool, reserveLibrary;
let deployer, attacker;
// Constants for the attack
const LARGE_LOAN = ethers.utils.parseEther("1000000");
const ATTACK_RATE = ethers.utils.parseUnits("0.5", 27); // 50% APR in RAY
const OPTIMAL_TIME_DELTA = 3600; // 1 hour
before(async function() {
[deployer, attacker] = await ethers.getSigners();
// Deploy contracts
const ReserveLibrary = await ethers.getContractFactory("ReserveLibrary");
reserveLibrary = await ReserveLibrary.deploy();
const LendingPool = await ethers.getContractFactory("LendingPool", {
libraries: {
ReserveLibrary: reserveLibrary.address
}
});
lendingPool = await LendingPool.deploy();
// Setup initial state
await setupInitialState();
});
it("Should demonstrate interest rate manipulation through approximation error", async function() {
console.log("\nStarting Interest Rate Manipulation Attack");
// Step 1: Take out large loan to maximize impact
await lendingPool.connect(attacker).borrow(LARGE_LOAN);
console.log("Borrowed large position:", ethers.utils.formatEther(LARGE_LOAN));
// Record initial state
const initialState = await getLendingPoolState();
// Step 2: Front-run with precise timeDelta and rate values
await lendingPool.connect(attacker).updateInterestRate(ATTACK_RATE);
console.log("Front-run: Updated interest rate");
// Step 3: Wait optimal time to maximize approximation error
await time.increase(OPTIMAL_TIME_DELTA);
// Step 4: Trigger interest accrual calculation
await lendingPool.connect(attacker).updateState();
// Step 5: Back-run to profit from error
await lendingPool.connect(attacker).repay(LARGE_LOAN);
// Get final state and verify profit
const finalState = await getLendingPoolState();
const profit = finalState.attacker_balance.sub(initialState.attacker_balance);
console.log("Attack completed. Profit:", ethers.utils.formatEther(profit));
expect(profit).to.be.gt(0);
});
async function setupInitialState() {
// Setup code...
}
async function getLendingPoolState() {
// State fetching code...
}
});

Tools Used

  • Manual code review

  • Hardhat for testing and PoC

  • Mathematical analysis tools for Taylor series error calculation

Recommended Mitigation

  1. Implement a more accurate exponential calculation for large values:

function calculateCompoundedInterest(
uint256 rate,
uint256 timeDelta
) internal pure returns (uint256) {
if (timeDelta < 1) {
return WadRayMath.RAY;
}
uint256 ratePerSecond = rate.rayDiv(SECONDS_PER_YEAR);
uint256 exponent = ratePerSecond.rayMul(timeDelta);
// Use more accurate calculation for large exponents
if (exponent > LARGE_EXPONENT_THRESHOLD) {
return _calculateLargeExponent(exponent);
}
// Use existing Taylor series for small exponents
return rayExp(exponent);
}
function _calculateLargeExponent(uint256 x) private pure returns (uint256) {
// Split large exponents into smaller parts
uint256 parts = x / SPLIT_THRESHOLD;
uint256 remainder = x % SPLIT_THRESHOLD;
uint256 result = WadRayMath.RAY;
// Calculate e^(SPLIT_THRESHOLD) using lookup table
uint256 partResult = EXPONENTIAL_LOOKUP[SPLIT_THRESHOLD];
// Apply full parts
for (uint256 i = 0; i < parts; i++) {
result = result.rayMul(partResult);
}
// Apply remainder using Taylor series
if (remainder > 0) {
result = result.rayMul(rayExp(remainder));
}
return result;
}
  1. Add rate change limits:

uint256 constant MAX_RATE_CHANGE_PER_SECOND = 100; // 1% max change per second
function validateRateChange(uint256 oldRate, uint256 newRate, uint256 timeDelta) internal pure {
uint256 maxChange = MAX_RATE_CHANGE_PER_SECOND * timeDelta;
uint256 actualChange = oldRate > newRate ? oldRate - newRate : newRate - oldRate;
require(
actualChange <= maxChange,
"Rate change exceeds maximum allowed"
);
}

Risk Rating

  • Severity: Critical

    • Manipulates core interest calculation

    • Allows unfair profit extraction

    • Affects protocol solvency

  • Likelihood: Medium-High

    • Requires precise timing and calculations

    • But can be automated with MEV bots

  • Impact: Critical

    • Direct financial loss for protocol and users

    • Undermines interest rate mechanism

    • No existing protections

This vulnerability is particularly dangerous because it affects the fundamental mathematics of the protocol's interest rate system. The complexity of the attack and the mathematical nature of the vulnerability make it harder to detect but also more devastating when exploited.

Updates

Lead Judging Commences

inallhonesty Lead Judge 5 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.