Category: Protocol Risks
PDT can be set as a reward token and withdrawn by admin
Medium Severity
Medium Impact
Medium Likelihood
Description
The function registerNewRewardToken
allows an admin address with the role TOKEN_MANAGER
to call it.
function registerNewRewardToken(address newRewardToken) external onlyRole(TOKEN_MANAGER) {
require(newRewardToken != address(0), "Invalid reward token");
uint256 numOfRewardTokens = rewardTokenList.length;
for (uint256 itTokenIndex = 0; itTokenIndex < numOfRewardTokens; ) {
if (rewardTokenList[itTokenIndex] == newRewardToken) {
revert DuplicatedRewardToken(newRewardToken);
}
unchecked {
++itTokenIndex;
}
}
// If newRewardToken is not found in rewardTokenList, then add it
rewardTokenList.push(newRewardToken);
emit RegisterNewRewardToken(currentEpochId, newRewardToken);
}
The registerNewRewardToken
function currently lacks a check to prevent the staked PDT token from being registered as a reward token.
Impact
Reward tokens owned by the StakedPDT contract can be withdrawn by the admin using withdrawRewardTokens
. This leads to a centralization risk as it allows a compromised admin account to withdraw PDT tokens staked by the users.
Recommendations
Add the following check to registerNewRewardToken
.
function registerNewRewardToken(address newRewardToken) external onlyRole(TOKEN_MANAGER) {
require(newRewardToken != pdt, "Invalid reward token");
Remediation
This issue has been acknowledged by Paragons DAO, and a fix was implemented in commit afcf3d16↗.