Dria

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

Phase Validation Bypass in BuyerAgent Enables Market Manipulation

Summary

The BuyerAgent contract allows withdrawal operations outside of designated phases, breaking the core market cycle guarantees and enabling potential market manipulation.

function withdraw(uint96 _amount) public onlyAuthorized {
(, Phase phase,) = getRoundPhase();
// @audit-info Critical: Withdrawal allowed in any phase with sufficient funds
if (phase != Phase.Withdraw) {
if (treasury() < minFundAmount() + _amount) {
revert MinFundSubceeded(_amount);
}
}
// @audit-info Dangerous: Direct transfer without phase enforcement
swan.token().transfer(owner(), _amount);
}
// @audit-info Inconsistent usage of phase validation
function _checkRoundPhase(Phase _phase) internal view returns (uint256, Phase) {
(uint256 round, Phase phase,) = getRoundPhase();
if (phase != _phase) {
revert InvalidPhase(phase, _phase);
}
return (round, phase);
}

Vulnerability Details

withdraw() succeeds outside Withdraw phase: https://github.com/Cyfrin/2024-10-swan-dria/blob/c8686b199daadcef3161980022e12b66a5304f8e/contracts/swan/BuyerAgent.sol#L262-L273

BuyerAgent.sol#function _checkRoundPhase: https://github.com/Cyfrin/2024-10-swan-dria/blob/c8686b199daadcef3161980022e12b66a5304f8e/contracts/swan/BuyerAgent.sol#L287-L294

function withdraw(uint96 _amount) public onlyAuthorized {
(, Phase phase,) = getRoundPhase();
// @audit-info Phase check bypass - allows withdrawals in any phase
// @audit-info Only enforces minFundAmount check, but doesn't restrict phase
if (phase != Phase.Withdraw) {
if (treasury() < minFundAmount() + _amount) {
revert MinFundSubceeded(_amount);
}
}
// @audit-info Direct transfer without phase enforcement
swan.token().transfer(owner(), _amount);
}
function _checkRoundPhase(Phase _phase) internal view returns (uint256, Phase) {
// @audit-info Phase validation helper used inconsistently
// @audit-info Some functions use this check while others bypass it
(uint256 round, Phase phase,) = getRoundPhase();
if (phase != _phase) {
revert InvalidPhase(phase, _phase);
}
return (round, phase);
}

Inconsistent phase enforcement creates a vulnerability in the protocol's state machine.

  • Withdrawal function bypasses strict phase validation

  • Inconsistent usage of _checkRoundPhase across functions

  • Direct token transfers without complete phase checks

Consider this example

// 1. BuyerAgent is deployed
// 2. Wait for Buy phase
// 3. Execute this sequence:
await buyerAgent.withdraw(amount); // Should fail in Buy phase but succeeds
await buyerAgent.purchase(); // Market state now compromised
// Verification steps:
const phase = await buyerAgent.getRoundPhase();
assert(phase === Phase.Buy); // Confirm we're in Buy phase
const tx = await buyerAgent.withdraw(amount);
assert(tx.success); // Withdrawal succeeds when it shouldn't

Impact

  • Users can withdraw funds during critical trading phases

  • Disrupts the intended Buy-Sell-Withdraw cycle

  • Creates timing advantages for sophisticated actors

Tools Used

Vs

Recommendations

Implement strict phase validation across all state-changing operations, explicit phase transition checks and events to track phase changes.

Consider implementing a time-delay mechanism for withdrawals to prevent flash loan attacks and market manipulation.

Updates

Lead Judging Commences

inallhonesty Lead Judge 12 months ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

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