Summary
When setting new tradingAccountToken, using setTradingAccountToken function, internal nextAccountId is not reset.
Root Cause
https://github.com/Cyfrin/2024-07-zaros/blob/d687fe96bb7ace8652778797052a38763fbcbb1b/src/perpetuals/branches/GlobalConfigurationBranch.sol#L179-L188
function setTradingAccountToken(address tradingAccountToken) external onlyOwner {
if (tradingAccountToken == address(0)) {
revert Errors.TradingAccountTokenNotDefined();
}
GlobalConfiguration.Data storage globalConfiguration = GlobalConfiguration.load();
globalConfiguration.tradingAccountToken = tradingAccountToken;
emit LogSetTradingAccountToken(msg.sender, tradingAccountToken);
}
Vulnerability details
setTradingAccountToken is used to set new trading account token address. TokenId is expected to start from tokenId = 1.
function createTradingAccount(
bytes memory referralCode,
bool isCustomReferralCode
)
public
virtual
returns (uint128 tradingAccountId)
{
GlobalConfiguration.Data storage globalConfiguration = GlobalConfiguration.load();
tradingAccountId = ++globalConfiguration.nextAccountId;
...
}
Proof of Concept
Not reseting nextAccountId can lead to some issues in the future. Consider this example scenario. Well funded attacker (for example other perpetual protocol) could try to mint total supply of the trading account token. In this scenario protocol customers are not able to access the perpetual markets. Protocol decides to change the trading account token address but users still can't mint new token because internal nextAccountId was not reset. While trying to mint new token it will revert as maximum tokenId has been reached even though no NFT were minted.
Impact
Protocol can be bricked.
Recommended Mitigation Steps
Reset nextAccountId when setting new trading account token address.
Example pseduocode:
function setTradingAccountToken(address tradingAccountToken) external onlyOwner {
if (tradingAccountToken == address(0)) {
revert Errors.TradingAccountTokenNotDefined();
}
GlobalConfiguration.Data storage globalConfiguration = GlobalConfiguration.load();
globalConfiguration.tradingAccountToken = tradingAccountToken;
+ globalConfiguration.nextAccountId = 0;
emit LogSetTradingAccountToken(msg.sender, tradingAccountToken);
}