Dria

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

Reentrancy vulnerability in the BuyerAgent.purchase() function.

Summary : The issue here is a reentrancy vulnerability in the BuyerAgent.purchase() function. Reentrancy occurs when a contract calls another contract, and that contract calls back into the original contract, potentially allowing an attacker to drain funds or manipulate state.

Vulnerability Details : The BuyerAgent.purchase() function makes an external call to swan.purchase(asset) at line 251, and then writes to state variables inventory[round].push(asset) at line 248, isOracleRequestProcessed[taskId] = true at line 255, and spendings[round] += price at line 242.

Impact : An attacker could potentially exploit this by:

  1. Creating a malicious contract that inherits from BuyerAgent.

  2. Overriding the purchase() function to call back into the original BuyerAgent contract's purchase() function.

  3. Manipulating the state variables to drain funds or gain unauthorized access.

Proof of Concept Code : Here's a proof of concept for the reentrancy vulnerability in the BuyerAgent.purchase() function:

pragma solidity ^0.8.0;
contract ReentrancyAttack {
BuyerAgent public buyerAgent;
Swan public swan;
constructor(address _buyerAgent, address _swan) public {
buyerAgent = BuyerAgent(_buyerAgent);
swan = Swan(_swan);
}
function attack() public {
// Call the purchase function on the BuyerAgent contract
buyerAgent.purchase();
// Reenter the purchase function by calling it again
buyerAgent.purchase();
}
// Override the fallback function to reenter the purchase function
fallback() external {
// Reenter the purchase function
buyerAgent.purchase();
}

Deployment and Execution:

  1. Deploy the BuyerAgent contract and the Swan contract.

  2. Deploy the ReentrancyAttack contract, passing the addresses of the BuyerAgent and Swan contracts as constructor arguments.

  3. Call the attack function on the ReentrancyAttack contract.

Expected Behavior:

The attack function should call the purchase function on the BuyerAgent contract, which should then call the purchase function on the Swan contract. However, due to the reentrancy vulnerability, the purchase function on the BuyerAgent contract will be called again, allowing the attacker to drain funds or manipulate state.

Actual Behavior:

The attack function will successfully drain funds from the BuyerAgent contract by repeatedly calling the purchase function and reentering the contract.

Tools Used : Slither, VS code

Recommendations : To fix this issue, you can use the checks-effects-interactions pattern:

  1. Check the conditions and requirements for the purchase.

  2. Update the state variables (effects).

  3. Make the external call to swan.purchase(asset).

Here's an example of how the purchase() function could be modified:

function purchase() external onlyAuthorized {
// Check conditions and requirements
(uint256 round,) = \_checkRoundPhase(Phase.Buy);
// Update state variables
uint256 taskId = oraclePurchaseRequests[round];
if (isOracleRequestProcessed[taskId]) {
revert TaskAlreadyProcessed();
}
// Read oracle result using the latest task id for this round
bytes memory output = oracleResult(taskId);
address[] memory assets = abi.decode(output, (address[]));
// Update state variables
for (uint256 i = 0; i < assets.length; i++) {
address asset = assets[i];
// Must not exceed the roundly buy-limit
uint256 price = swan.getListingPrice(asset);
spendings[round] += price;
if (spendings[round] > amountPerRound) {
revert BuyLimitExceeded(spendings[round], amountPerRound);
}
// Add to inventory
inventory[round].push(asset);
}
// Make external call
swan.purchase(asset);
}

By following the checks-effects-interactions pattern, you can prevent reentrancy attacks and ensure the security of your contract.

Updates

Lead Judging Commences

inallhonesty Lead Judge 8 months ago
Submission Judgement Published
Invalidated
Reason: Lack of quality

Support

FAQs

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