Summary
The DebtToken contract deploys with an uninitialized _reservePool
address, causing all balance and supply queries to revert since they depend on the ReservePool's getNormalizedDebt()
function.
Vulnerability Details
_reservePool
is not set in the constructor. Calls to balanceOf
/totalSupply
will revert until set, rendering the contract unusable.
In DebtToken.sol#L97-L99, balanceOf()#L223-225, totalSupply()#L232-L234
constructor(string memory name, string memory symbol, address initialOwner)
ERC20(name, symbol)
ERC20Permit(name)
Ownable(initialOwner) {
if (initialOwner == address(0)) revert InvalidAddress();
_usageIndex = uint128(WadRayMath.RAY);
}
function balanceOf(address account) public view override(ERC20, IERC20) returns (uint256) {
uint256 scaledBalance = super.balanceOf(account);
return scaledBalance.rayMul(ILendingPool(_reservePool).getNormalizedDebt());
}
function totalSupply() public view override(ERC20, IERC20) returns (uint256) {
uint256 scaledSupply = super.totalSupply();
return scaledSupply.rayDiv(ILendingPool(_reservePool).getNormalizedDebt());
}
Impact
Contract functionality is blocked until resolved.
Tools Used
manual
Recommendations
Initialize _reservePool
during construction or enforce it via initial setup.
Constructor Initialization
constructor(
string memory name,
string memory symbol,
address initialOwner,
address reservePool
) ERC20(name, symbol) ERC20Permit(name) Ownable(initialOwner) {
if (initialOwner == address(0)) revert InvalidAddress();
if (reservePool == address(0)) revert InvalidAddress();
_usageIndex = uint128(WadRayMath.RAY);
_reservePool = reservePool;
}
Two-Step Initialization
function initialize(address reservePool) external onlyOwner {
if (_reservePool != address(0)) revert AlreadyInitialized();
if (reservePool == address(0)) revert InvalidAddress();
_reservePool = reservePool;
emit ReservePoolSet(reservePool);
}