Summary
The stabilityPool address can remain unset after deployment, potentially leading to a blocked contract state.
Vulnerability Details
The contract doesn't enforce setting the stabilityPool address during construction, and there's no check for zero address in core functions.
POC
const { expect } = require("chai");
const { ethers } = require("hardhat");
describe("DEToken Initialization Vulnerability", function () {
let deToken;
let owner;
let user;
beforeEach(async function () {
[owner, user] = await ethers.getSigners();
const DEToken = await ethers.getContractFactory("DEToken");
deToken = await DEToken.deploy(
"Debitum Emptor",
"DE",
owner.address,
"0x1234567890123456789012345678901234567890"
);
await deToken.deployed();
});
it("should fail all operations when stabilityPool is not set", async function () {
await expect(
deToken.mint(user.address, ethers.utils.parseEther("1"))
).to.be.revertedWith("OnlyStabilityPool");
expect(await deToken.getStabilityPool()).to.equal(
ethers.constants.AddressZero
);
});
});
Impact
Severity: Medium
Contract becomes unusable until stabilityPool is set
No explicit checks preventing operations with unset stabilityPool
Could lead to locked funds if tokens are sent before setup
Tools Used
Recommendations
Add initialization status tracking:
contract DEToken is ERC20, ERC20Permit, IDEToken, Ownable {
bool private _initialized;
event Initialized();
error NotInitialized();
modifier whenInitialized() {
if (!_initialized) revert NotInitialized();
_;
}
function setStabilityPool(address newStabilityPool) external onlyOwner {
if (newStabilityPool == address(0)) revert InvalidAddress();
address oldStabilityPool = stabilityPool;
stabilityPool = newStabilityPool;
if (!_initialized) {
_initialized = true;
emit Initialized();
}
emit StabilityPoolUpdated(oldStabilityPool, newStabilityPool);
}
function mint(address to, uint256 amount) external override
onlyStabilityPool
whenInitialized
{
}
}
Add initialization check to core functions:
function transfer(address recipient, uint256 amount) public override(ERC20,IERC20)
onlyStabilityPool
whenInitialized
returns (bool)
{
return super.transfer(recipient, amount);
}
function transferFrom(address sender, address recipient, uint256 amount) public override(ERC20,IERC20)
onlyStabilityPool
whenInitialized
returns (bool)
{
return super.transferFrom(sender, recipient, amount);
}
Add recovery mechanism for stuck tokens:
function rescueTokens(address token, address to, uint256 amount) external onlyOwner {
if (token == rTokenAddress) revert CannotRescueRToken();
if (token == address(this)) revert CannotRescueDEToken();
IERC20(token).safeTransfer(to, amount);
emit TokensRescued(token, to, amount);
}