52203 sc medium griefing attack on arctokenpurchase setpurchasetoken function via front running

Submitted on Aug 8th 2025 at 17:24:09 UTC by @thesvn for Attackathon | Plume Network

  • Report ID: #52203

  • Report Type: Smart Contract

  • Report severity: Medium

  • 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

    • Permanent freezing of funds

Description

Brief/Intro

The setPurchaseToken function in the ArcTokenPurchase contract can be permanently blocked by malicious actors through a griefing attack. Once a purchase token is set and any token is enabled for sale, an attacker can prevent the DEFAULT_ADMIN_ROLE from ever changing the purchase token by maintaining at least one enabled token. This creates a denial-of-service condition that prevents the protocol from upgrading or migrating to a different purchase token, even when necessary due to technical issues, regulatory requirements, or protocol improvements.

Vulnerability Details

The vulnerability exists in the setPurchaseToken function of ArcTokenPurchase.sol. The function includes a check that prevents changing the purchase token when there are active sales:

function setPurchaseToken(
    address purchaseTokenAddress
) external onlyRole(DEFAULT_ADMIN_ROLE) {
    PurchaseStorage storage ps = _getPurchaseStorage();
    if (ps.enabledTokens.length() > 0) {
        revert CannotChangePurchaseTokenWithActiveSales();
    }
    if (purchaseTokenAddress == address(0)) {
        revert InvalidPurchaseTokenAddress();
    }
    ps.purchaseToken = IERC20(purchaseTokenAddress);
    emit PurchaseTokenUpdated(purchaseTokenAddress);
}

The fundamental problem is that once any token is enabled for sale, the contract permanently blocks the DEFAULT_ADMIN_ROLE from changing the purchase token. This means that if the protocol ever needs to upgrade, migrate, or recover from a misconfiguration of the purchase token (for example, due to a bug, a depegged stablecoin, or a regulatory requirement), it is impossible to do so without disabling all active sales. A malicious token creator can enable a sale and the DEFAULT_ADMIN_ROLE is forever prevented from updating the purchase token.

Impact Details

References

Proof of Concept

1

Initial Setup

  • ArcTokenPurchase contract is deployed and initialized

  • Admin sets USDC as the purchase token

  • Contract is operational with USDC as purchase currency

2

Attacker Preparation

  • Attacker calls ArcTokenFactory.createToken() to create a new ArcToken

  • Attacker becomes the token admin with full privileges

  • Attacker transfers some tokens to ArcTokenPurchase contract

3

Griefing Attack

  • Attacker calls ArcTokenPurchase.enableToken(attackerToken, amount, price)

  • Function succeeds; enabledTokens.length() becomes 1

4

Admin Attempt to Change Purchase Token

  • Admin tries to call setPurchaseToken(USDT_ADDRESS) to migrate from USDC to USDT

  • Function reverts with CannotChangePurchaseTokenWithActiveSales()

  • Admin is permanently blocked from changing the purchase token while any token remains enabled

5

Protocol State

  • Purchase token remains locked to USDC

  • Protocol cannot upgrade to USDT or any other token while the enabled token exists

  • All future purchase token changes are blocked while any token is enabled

Was this helpful?