51296 sc low arctokenpurchase withdrawal breaks view functions
Submitted on Aug 1st 2025 at 14:11:09 UTC by @funkornaut for Attackathon | Plume Network
Report ID: #51296
Report Type: Smart Contract
Report severity: Low
Target: https://github.com/immunefi-team/attackathon-plume-network/blob/main/arc/src/ArcTokenPurchase.sol
Impacts:
Contract fails to deliver promised returns, but doesn't lose value
Description
Brief/Intro
The withdrawUnsoldArcTokens() function in ArcTokenPurchase.sol contains an accounting logic error that causes multiple view functions (getMaxNumberOfTokens() and getTokenInfo()) to return incorrect values after admin withdrawals. When token administrators withdraw unsold tokens, the function correctly transfers the tokens but fails to update the internal sale accounting, leading to persistent discrepancies between reported and actual token availability.
Vulnerability Details
The vulnerability exists because withdrawUnsoldArcTokens() only performs the token transfer without updating the TokenInfo accounting:
function withdrawUnsoldArcTokens(
address _tokenContract,
address to,
uint256 amount
) external onlyTokenAdmin(_tokenContract) {
// ... validation checks ...
ArcToken token = ArcToken(_tokenContract);
uint256 contractBalance = token.balanceOf(address(this));
if (contractBalance < amount) {
revert InsufficientUnsoldTokens();
}
bool success = token.transfer(to, amount); // Transfers tokens
if (!success) {
revert ArcTokenWithdrawalFailed();
}
// MISSING: TokenInfo accounting update
// Should include: info.totalAmountForSale -= amount;
}Meanwhile, both affected view functions rely on the unupdated accounting:
function getMaxNumberOfTokens(address _tokenContract) external view returns (uint256) {
TokenInfo storage info = _getPurchaseStorage().tokenInfo[_tokenContract];
return info.totalAmountForSale - info.amountSold; // Returns stale data after withdrawals
}
function getTokenInfo(address _tokenContract) external view returns (TokenInfo memory) {
return _getPurchaseStorage().tokenInfo[_tokenContract]; // Returns struct with wrong totalAmountForSale
}TokenInfo Struct Corruption:
struct TokenInfo {
bool isEnabled; // ✅ Correct
uint256 tokenPrice; // ✅ Correct
uint256 totalAmountForSale; // ❌ CORRUPTED - Not reduced by withdrawals
uint256 amountSold; // ✅ Correct (updated by purchases)
}Impact Details
When an admin withdraws Arc tokens and there is an ongoing arc token sale the view functions to get token info and the max number of tokens available for purchase will be incorrect. This may cause issues with user facing front end and overall user experience.
References
https://github.com/immunefi-team/attackathon-plume-network/blob/main/arc/src/ArcTokenPurchase.sol#L419-#L429 https://github.com/immunefi-team/attackathon-plume-network/blob/main/arc/src/ArcTokenPurchase.sol#L359-#L363 https://github.com/immunefi-team/attackathon-plume-network/blob/main/arc/src/ArcTokenPurchase.sol#L347-#L350
Link to Proof of Concept
https://gist.github.com/Funkornaut/cd3844097890ece413201eba6ef6ff4a
Proof of Concept
Was this helpful?