Dria

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

Race Condition in Market Parameter Initialization Creates Incorrect Round Calculations

Description:
https://github.com/Cyfrin/2024-10-swan-dria/blob/c8686b199daadcef3161980022e12b66a5304f8e/contracts/swan/BuyerAgent.sol#L138
https://github.com/Cyfrin/2024-10-swan-dria/blob/c8686b199daadcef3161980022e12b66a5304f8e/contracts/swan/SwanManager.sol#L80-L84

The Swan protocol's market timing system relies on accurate initialization of marketParameterIdx in the BuyerAgent contract. However, there's a critical race condition in the initialization process that can lead to incorrect phase and round calculations throughout the agent's lifetime.

Key components involved:

  1. Swan.createBuyer() - Creates new buyer agents

  2. BuyerAgent.constructor() - Initializes market parameter index

  3. SwanManager.setMarketParameters() - Updates market parameters

  4. BuyerAgent.getRoundPhase() - Calculates current phase based on market parameters

The vulnerability stems from the following sequence:

// In BuyerAgent constructor
constructor(...) {
// Vulnerable initialization
marketParameterIdx = swan.getMarketParameters().length - 1;
}
// In SwanManager
function setMarketParameters(SwanMarketParameters memory _marketParameters) external onlyOwner {
_marketParameters.timestamp = block.timestamp;
marketParameters.push(_marketParameters);
}

Impact:

  1. Phase Calculation Errors:

    • Agents could operate in wrong phases (Buy/Sell/Withdraw)

    • Incorrect timing windows for asset purchases

    • Misaligned market cycles between agents

  2. Financial Implications:

    • Incorrect spending period calculations

    • Potential manipulation of market timing

    • Incorrect spending period calculations

Proof of Concept:

// Test Scenario
1. Initial state: marketParameters.length = 2
2. In same block:
tx1: Deploy BuyerAgent (reads length = 2, sets idx = 1)
tx2: Owner adds new parameter (length becomes 3)
3. Result: BuyerAgent uses wrong parameter forever
// Impact on getRoundPhase():
function getRoundPhase() public view returns (uint256, Phase, uint256) {
// Always uses wrong parameter index
if (marketParams.length == marketParameterIdx + 1) {
return _computePhase(marketParams[marketParameterIdx], ...);
}
// ... rest of function
}

Recommended Mitigation:

  1. Add timestamp validation in SwanManager:

function setMarketParameters(SwanMarketParameters memory _marketParameters) external onlyOwner {
require(_marketParameters.timestamp > block.timestamp, "Future timestamp required");
require(
marketParameters.length == 0 ||
_marketParameters.timestamp > marketParameters[marketParameters.length - 1].timestamp,
"Timestamps must increase"
);
marketParameters.push(_marketParameters);
}

2.Improve BuyerAgent initialization:

constructor(...) {
SwanMarketParameters[] memory params = swan.getMarketParameters();
// Find correct parameter for current block
for(uint i = params.length - 1; i >= 0; i--) {
if(params[i].timestamp <= block.timestamp) {
marketParameterIdx = i;
break;
}
}
require(marketParameterIdx != type(uint256).max, "No valid parameters");
}

3.Consider moving parameter index assignment to factory:

function deploy(...) external returns (BuyerAgent) {
uint256 correctIdx = findCorrectParameterIndex();
return new BuyerAgent(..., correctIdx);
}
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.