20,000 USDC
View results
Submission Details
Severity: low
Valid

Possible incorrect borrowing timestamps

Summary

The smart contract function borrow allows users to borrow loans from a pool. However, the function's dependency on the block.timestamp for recording the borrowing timestamp introduces a vulnerability. Malicious miners could potentially manipulate the block.timestamp, leading to inaccuracies in the recorded borrowing timestamps. This can have implications for time-sensitive operations such as interest calculations, time-based auctions, and loan durations.

Vulnerability Details

Loan memory loan = Loan({
// ...
startTimestamp: block.timestamp, // Borrowing timestamp is recorded here
// ...
});

The block.timestamp represents the time at which the block is mined and is determined by the miner. Although Ethereum has mechanisms in place to prevent blatant timestamp manipulation, miners still have some degree of control over the block timestamp within certain limits, known as "block time drift." This drift allows miners to adjust the timestamp by a small range, which introduces a possibility of timestamp manipulation.

Impact

The timestamp manipulation vulnerability can lead to incorrect borrowing timestamps being recorded in the smart contract. As a result, time-sensitive operations, such as calculating interest based on loan start times, determining loan durations, and initiating time-based auctions, can be affected. Inaccurate timestamps may lead to incorrect interest calculations, unintended loan durations, and potential financial losses for both lenders and borrowers.

Tools Used

Manual

Recommendations

Using block.number, which represents the current block number in the Ethereum blockchain, in combination with block.timestamp, can add an additional layer of security. By incorporating block.number, the smart contract can reduce the risk of timestamp manipulation while still ensuring that the recorded timestamp remains close to the actual time the transaction is mined.

// Import the oracle contract or interface here
function borrow(Borrow[] calldata borrows) public {
for (uint256 i = 0; i < borrows.length; i++) {
// ...
uint256 currentBlockNumber = block.number;
uint256 externalTimestamp = // Fetch the timestamp from a trusted external oracle here
require(externalTimestamp >= block.timestamp, "Invalid timestamp"); // Ensuring the external timestamp is not in the past
require(externalTimestamp <= block.timestamp.add(maxTimestampDrift), "Invalid timestamp"); // Allowing for a maximum timestamp drift
// Use a combination of block.timestamp and block.number for the startTimestamp
uint256 actualTimestamp = externalTimestamp.add(currentBlockNumber.sub(startBlockNumber).mul(blockTime));
Loan memory loan = Loan({
// ...
startTimestamp: actualTimestamp, // Using the combined timestamp
// ...
});
// ...
}
}

In this example, block.number is used to calculate the drift in block time since the transaction was mined. This is done by storing the block.number at the start of the transaction in startBlockNumber and calculating the number of blocks that have elapsed since then.

Additionally, maxTimestampDrift is used to specify the maximum allowed drift in the externally fetched timestamp. The contract ensures that the externally fetched timestamp is within an acceptable range from the actual transaction time by checking externalTimestamp <= block.timestamp.add(maxTimestampDrift).

By combining block.number and block.timestamp in this manner, the smart contract can mitigate the risk of timestamp manipulation while still maintaining the ability to record an accurate and secure borrowing timestamp.

Support

FAQs

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