DeFiHardhat
12,000 USDC
View results
Submission Details
Severity: low
Invalid

Oracle Manipulation

Summary

Oracle Manipulation.

NOTE: the vulnerability is in the MultiFlowPump::update not in Well contract, it is in Scope.

Vulnerability Details

users can interact with well via basic.exchange app or directly by using his own deployed smart contract
example: bad_actor can call swapTo/swapFrom/addLiquidity via his smart contract.
the bug arise when the bad_actor calls 2 Well functions in one transaction the 2 calls will use the same block.timestamp
for example:

// bad_actor contract.
function call_Well(address well) public {
IWell(well).addLiquidity(tokenAmountsIn, minLpAmountOut, recipient, deadline); // <- timestamp: 1700000000
IWell(well).addLiquidity(tokenAmountsIn, minLpAmountOut, recipient, deadline); // <- timestamp: 1700000000
}

bad_actor will call addLiquidity/swapTo/swapFrom twice with same block.timestamp in this Scenario this what will happen:

  1. bad_actor calls addLiquidity/swapTo/swapFrom etc.. twice from call_Well function.

  2. this will call Well functions twice (addLiquidity/addLiquidity) with same block.timestamp.

  3. first addLiquidity call will execute _updatePumps and pumpState.lastReserves will be updated with new timestamp example: 1700000000

  4. second addLiquidity call will execute _updatePumps but at this point the pumpState.lastReserves wont update because of this lines deltaTimestamp:

uint256 deltaTimestamp = _getDeltaTimestamp(pumpState.lastTimestamp);
// If no time has passed, don't update the pump reserves.
if (deltaTimestamp == 0) return;
  1. first call store pumpState.lastTimestamp 1700000000 and since the second call timestamp is also 1700000000 the deltaTimestamp will be:
    1700000000 - 1700000000 -> 0

  2. the pumpState.lastReserves not updated but the reserves in Well contract are updated which create a gap between the current Well reserves and the pump lastReserves.

  3. according to the Docs the Pump should be updated upon each interaction with a Well adds/swaps but this can be bypassed:

POC: Add this test is Pump.Update.t.sol

// Add this test is Pump.Update.t.sol
function test_OracleManipulation() public prank(user) {
uint256 lastTimestamp = block.timestamp;
// first time won't store in pumpState.lastReserves
b[0] = 1e18;
b[1] = 1e18;
mWell.update(address(pump), b, data);
// second time will execute _init and store pumpState.lastReserves
b[0] = 3e18;
b[1] = 3e18;
vm.warp(lastTimestamp + 100);
mWell.update(address(pump), b, data);
// Users interact with Well addLiquidity/swapTo/swapFrom etc...
vm.warp(lastTimestamp + 400);
b[0] = 7e18;
b[1] = 7e18;
// @audit now user calls ie: addLiquidity twice from a function
// this will make block.timestamp same for first/second addLiquidity calls.
// at this point the Well reserves will be updated but the pump lastReserves
// will not be updated because of the same timestamp.
mWell.update(address(pump), b, data); // <- example: timestamp: 1700000000
// @audit This will return because of check: `if (deltaTimestamp == 0) return;`
// NOTE: comment/uncomment this line to see the difference.
// vm.warp(lastTimestamp + 700);
mWell.update(address(pump), b, data); // <- example: timestamp: 1700000000
uint256[] memory readLastCappedReserves = pump.readLastCappedReserves(address(mWell), data);
console.log(readLastCappedReserves[0], readLastCappedReserves[1]);
uint256[] memory readLastInstantaneousReserves = pump.readLastInstantaneousReserves(address(mWell), data);
console.log(readLastInstantaneousReserves[0], readLastInstantaneousReserves[1]);
}

Result:

1) this is the expected result of readLastCappedReserves/readLastInstantaneousReserves:
readLastCappedReserves : 34665644616707268 34665644616707268
readLastInstantaneousReserves: 34665644616700683 34665644616700683
2) but this is the result we get if Well reserves updated but pump not updated:
readLastCappedReserves : 1372833296782 1372833296782
readLastInstantaneousReserves: 1372833296781 1372833296781

Impact

NOTE: the vulnerability is in the MultiFlowPump::update not in Well contract, it is in Scope.

The impact is similar to these previous findings but the main difference is that these findings in Well but this bug is in update function but the idea is the same,
reservers in Well are updated but the reserves in Pump are not updated which will cause an Oracle manipulation.

H-1 Finding

H-2 Finding

  • According to the Docs the Pump should be updated upon each interaction with a Well adds/swaps but this can be bypassed:

Tools Used

Recommendations

Updates

Lead Judging Commences

giovannidisiena Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Design choice
Assigned finding tags:

Informational/Invalid

giovannidisiena Lead Judge
about 1 year ago
giovannidisiena Lead Judge
about 1 year ago
giovannidisiena Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Design choice
Assigned finding tags:

Informational/Invalid

Support

FAQs

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