DeFiHardhatFoundry
250,000 USDC
View results
Submission Details
Severity: medium
Invalid

Off-by-one error in runBlueprint modifier in TractorFacet

Summary

The main issue here that the blueprint's end time condition was not correctly handling the exact end time scenario. The condition used block.timestamp <= requisition.blueprint.endTime which caused transactions that occur precisely at the end time to fail. This issue was found in the runBlueprint modifier of the
TractorFacet contract.

At first, I presumed the conditional statement would hold, but after running a test, which you can see below, the strict conditional statement does not always hold.

```

block.timestamp <= requisition.blueprint.endTime,
modifier runBlueprint(LibTractor.Requisition calldata requisition) {
require(
LibTractor._getBlueprintNonce(requisition.blueprintHash) <
requisition.blueprint.maxNonce,
"TractorFacet: maxNonce reached"
);
require(
requisition.blueprint.startTime <= block.timestamp &&
block.timestamp <= requisition.blueprint.endTime,
"TractorFacet: blueprint is not active"
);
LibTractor._incrementBlueprintNonce(requisition.blueprintHash);
LibTractor._setPublisher(payable(requisition.blueprint.publisher));
_;
LibTractor._resetPublisher();
}

Vulnerability Details

A test was added to verify that the transaction is executed successfully exactly at the blueprint's end time.

it("should succeed to execute exactly at end time", async function () {
const currentTimestamp = (await ethers.provider.getBlock('latest')).timestamp;
const endTime = currentTimestamp + 60; // Set end time to 60 seconds in the future
const validData = this.farmFacet.interface.encodeFunctionData("advancedFarm", [
[] // Provide appropriate arguments for the advancedFarm function
]);
const endTimeBlueprint = {
...this.blueprint,
endTime: endTime,
data: validData
};
const endTimeRequisition = {
blueprint: endTimeBlueprint,
blueprintHash: await this.tractorFacet.connect(publisher).getBlueprintHash(endTimeBlueprint)
};
await signRequisition(endTimeRequisition, publisher);
await this.tractorFacet.connect(publisher).publishRequisition(endTimeRequisition);
// Move time to exactly the end time
await time.increaseTo(endTime);
// Check the current block timestamp to ensure we are at the expected time
const newTimestamp = (await ethers.provider.getBlock('latest')).timestamp;
console.log("Current block timestamp:", newTimestamp);
console.log("Blueprint end time:", endTime);
// Log the timestamps before calling the function
console.log("Timestamp check - should be true:", newTimestamp === endTime);
// Try executing exactly at the end time
try {
await this.tractorFacet.connect(operator).tractor(endTimeRequisition, ethers.utils.hexlify("0x"));
console.log("Tractor executed successfully at end time");
} catch (error) {
console.log("Transaction reverted with error message:", error.message);
throw error;
}
});

OUTPUT OF TEST:

Run Tractor
Current block timestamp: 1719230796
Blueprint end time: 1719230796
Timestamp check - should be true: true
Transaction reverted with error message: VM Exception while processing transaction: reverted with reason string 'TractorFacet: blueprint is not active'

Impact

The transactions that were supposed to be valid exactly at the blueprint's end time were being incorrectly rejected. This creates a scenario where a valid transaction, if attempted precisely at the end time, fails with the reason "TractorFacet: blueprint is not active".

Tools Used

Hardhat/Manual Reveiw

Recommendations

The issue was due to an incorrect handling of the end time condition in the runBlueprint modifier. By updating the condition to requisition.blueprint.endTime + 1, the contract now correctly includes the exact end time as valid, ensuring that transactions at this time do not fail unexpectedly.

Running the test again with the below changes, you see the conditional statement holds true and the test passes.

currentTimestamp <= requisition.blueprint.endTime + 1, // Adding +1 for the conditional statement to hold true
Updates

Lead Judging Commences

inallhonesty Lead Judge 12 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Appeal created

golanger85 Submitter
12 months ago
inallhonesty Lead Judge
12 months ago
inallhonesty Lead Judge 11 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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