Dria

Swan
NFTHardhat
21,000 USDC
View results
Submission Details
Severity: medium
Invalid

Token Blacklist Vulnerability Can Permanently Lock Staked Funds

Summary

The registry contract permanently holds staked tokens. If the contract address gets blacklisted by tokens with blacklist functionality (like USDC/USDT), all staked tokens become permanently locked, with no recovery mechanism.

https://github.com/Cyfrin/2024-10-swan-dria/blob/main/contracts/llm/LLMOracleRegistry.sol#L94

function register(LLMOracleKind kind) public {
uint256 amount = getStakeAmount(kind);
token.transferFrom(msg.sender, address(this), amount);
registrations[msg.sender][kind] = amount;
}

Many widely used ERC20 tokens (like USDC, USDT) implement blacklisting functionality where certain addresses can be blocked from transferring tokens. If the registry contract address gets blacklisted:

  1. Any tokens already held by the contract become permanently locked

  2. All registered oracles lose their staked tokens

  3. The unregister function's token approval becomes useless since transfers are blocked

  4. No recovery mechanism exists in the current contract

Real World Example:

  • An oracle stakes 10,000 USDC to register as a Generator

  • The registry contract gets blacklisted by USDC (could happen due to regulatory requirements)

  • Even though the oracle can call unregister(), they can never get their USDC back

  • The tokens remain locked in the contract forever

Impact

  • Permanent loss of user funds

  • Contract becomes unusable for that specific token

  • No remediation possible even by contract owner

  • All oracles using that token must be considered compromised

Fix

Safer Pattern:

contract LLMOracleRegistry {
// Track pending withdrawals separately
mapping(address => uint256) private pendingWithdrawals;
// Split stake release into two steps
function unregister(LLMOracleKind kind) public {
uint256 amount = registrations[msg.sender][kind];
delete registrations[msg.sender][kind];
pendingWithdrawals[msg.sender] += amount;
}
function withdraw() public {
uint256 amount = pendingWithdrawals[msg.sender];
require(amount > 0, "No pending withdrawal");
pendingWithdrawals[msg.sender] = 0;
require(token.transfer(msg.sender, amount), "Transfer failed");
}
// Allow changing token address in emergency
function setToken(address newToken) external onlyOwner {
require(newToken != address(0), "Zero address");
token = IERC20(newToken);
}
}

Mitigation Strategies:

  1. Implement pull payment pattern for withdrawals

  2. Add ability to change token address in emergencies

  3. Consider multi-token support to spread risk

  4. Add emergency withdrawal functionality for owner

  5. Consider time-locks for large withdrawals

This issue is particularly critical because:

  • USDC/USDT are likely token choices for staking

  • Blacklisting is a real and active risk with these tokens

  • The amounts at stake could be substantial

  • The contract is designed for long-term staking

  • Once blacklisted, the loss is irreversible

Updates

Lead Judging Commences

inallhonesty Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Out of scope

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.