Dria

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

Fund Drainage Through Withdrawal Arithmetic Overflow. The check treasury() < minFundAmount() + _amount can overflow with large _amount values

Summary

The BuyerAgent contract's withdrawal mechanism contains an arithmetic overflow vulnerability that allows bypassing minimum fund requirements during non-Withdraw phases. This enables malicious actors to drain funds below the required minimum balance, potentially disrupting the entire buying cycle and oracle operations.

The vulnerability exists in the withdrawal logic where unsafe arithmetic operations are performed before balance validation: https://github.com/Cyfrin/2024-10-swan-dria/blob/c8686b199daadcef3161980022e12b66a5304f8e/contracts/swan/BuyerAgent.sol#L262-L271

function withdraw(uint96 _amount) public onlyAuthorized {
(, Phase phase,) = getRoundPhase();
// @Issue - Unsafe addition allows overflow, bypassing minimum balance check
if (phase != Phase.Withdraw) {
if (treasury() < minFundAmount() + _amount) {
revert MinFundSubceeded(_amount);
}
}
// @Issue - Transfer executes after compromised check
swan.token().transfer(owner(), _amount);
}

The check minFundAmount() + _amount can overflow with carefully crafted amounts, making the comparison pass even when it should fail. This bypasses the core protection mechanism meant to maintain minimum operating funds.

Vulnerability Details

The reason is because the contract performs unsafe arithmetic when checking withdrawal conditions: https://github.com/Cyfrin/2024-10-swan-dria/blob/c8686b199daadcef3161980022e12b66a5304f8e/contracts/swan/BuyerAgent.sol#L270

if (treasury() < minFundAmount() + _amount)

This addition can overflow with large _amount values, making the comparison pass even when it shouldn't, allowing unauthorized withdrawals below the minimum required balance.

#Withdraw: https://github.com/Cyfrin/2024-10-swan-dria/blob/c8686b199daadcef3161980022e12b66a5304f8e/contracts/swan/BuyerAgent.sol#L262-L273

function withdraw(uint96 _amount) public onlyAuthorized {
(, Phase phase,) = getRoundPhase();
// @Issue - Unsafe arithmetic: minFundAmount() + _amount can overflow
if (phase != Phase.Withdraw) {
if (treasury() < minFundAmount() + _amount) {
revert MinFundSubceeded(_amount);
}
}
// @Issue - Transfer executes after potentially compromised check
swan.token().transfer(owner(), _amount);
}
  1. During Buy/Sell phases, the contract must maintain a minimum balance (minFundAmount) to:

    • Cover oracle fees

    • Ensure sufficient funds for next round purchases

    • Maintain protocol economic security

  2. The overflow allows circumventing this protection by:

    • Making minFundAmount() + _amount wrap around to a small number

    • Passing the comparison check even with insufficient funds

    • Enabling withdrawal of more tokens than should be allowed

See example below:

// 1. Deployed BuyerAgent with initial funds of 1000 tokens
// 2. Set minFundAmount to 100 tokens
// 3. During Buy phase, call withdraw with amount = type(uint96).max - 99
// 4. The check passes due to overflow: (100 + (type(uint96).max - 99)) overflows to small value
// 5. Allows withdrawing more than allowed, breaking minimum balance requirement

Impact

  • Circumvention of minimum balance requirements

  • Potential disruption of oracle operations due to insufficient fees

  • Breaking of round-based purchasing mechanisms

Recommendations

function withdraw(uint96 _amount) public onlyAuthorized {
(, Phase phase,) = getRoundPhase();
if (phase != Phase.Withdraw) {
- if (treasury() < minFundAmount() + _amount) {
+ uint256 currentBalance = treasury();
+ uint256 minRequired = minFundAmount();
+
+ if (_amount > currentBalance ||
+ currentBalance - _amount < minRequired) {
revert MinFundSubceeded(_amount);
}
}
swan.token().transfer(owner(), _amount);
}

The fix uses checked arithmetic and reverses the comparison logic to prevent overflow exploitation while maintaining the intended security invariants.

Updates

Lead Judging Commences

inallhonesty Lead Judge 12 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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