Skip to content

Latest commit

 

History

History
126 lines (95 loc) · 3.79 KB

M-03.md

File metadata and controls

126 lines (95 loc) · 3.79 KB

Sale end time is not checked in LPDA contract

The buy function present in the LPDA contract has a missing validation to check that the current sale hasn't finished at the time the function is called.

The contract has a round of validations around line 62:

https://github.com/code-423n4/2022-12-escher/blob/main/src/minters/LPDA.sol#L62

function buy(uint256 _amount) external payable {
    uint48 amount = uint48(_amount);
    Sale memory temp = sale;
    IEscher721 nft = IEscher721(temp.edition);
    require(block.timestamp >= temp.startTime, "TOO SOON");
    uint256 price = getPrice();
    require(msg.value >= amount * price, "WRONG PRICE");
    ...

It correctly checks the sale's start time but fails to check the end time.

Impact

A buyer is allowed to still call the buy method after the sale has ended.

PoC

The following test reproduces the issue:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

import "forge-std/Test.sol";
import {FixedPriceFactory} from "src/minters/FixedPriceFactory.sol";
import {FixedPrice} from "src/minters/FixedPrice.sol";
import {OpenEditionFactory} from "src/minters/OpenEditionFactory.sol";
import {OpenEdition} from "src/minters/OpenEdition.sol";
import {LPDAFactory} from "src/minters/LPDAFactory.sol";
import {LPDA} from "src/minters/LPDA.sol";
import {Escher721} from "src/Escher721.sol";

contract AuditTest is Test {
    address deployer;
    address creator;
    address buyer;

    FixedPriceFactory fixedPriceFactory;
    OpenEditionFactory openEditionFactory;
    LPDAFactory lpdaFactory;

    function setUp() public {
        deployer = makeAddr("deployer");
        creator = makeAddr("creator");
        buyer = makeAddr("buyer");

        vm.deal(buyer, 1e18);

        vm.startPrank(deployer);

        fixedPriceFactory = new FixedPriceFactory();
        openEditionFactory = new OpenEditionFactory();
        lpdaFactory = new LPDAFactory();

        vm.stopPrank();
    }
    
    function test_LPDA_buy_EndTimeNotChecked() public {
        // Setup NFT and create sale
        vm.startPrank(creator);

        Escher721 nft = new Escher721();
        nft.initialize(creator, address(0), "Test NFT", "TNFT");

        uint48 startId = 0;
        uint48 finalId = 1;
        uint80 startPrice = 1e6;
        uint80 dropPerSecond = 1;
        uint96 startTime = uint96(block.timestamp);
        uint96 endTime = uint96(block.timestamp + 1 hours);

        LPDA.Sale memory sale = LPDA.Sale(
            startId, // uint48 currentId;
            finalId, // uint48 finalId;
            address(nft), // address edition;
            startPrice, // uint80 startPrice;
            0, // uint80 finalPrice;
            dropPerSecond, // uint80 dropPerSecond;
            endTime, // uint96 endTime;
            payable(creator), // address payable saleReceiver;
            startTime // uint96 startTime;
        );
        LPDA lpdaSale = LPDA(lpdaFactory.createLPDASale(sale));

        nft.grantRole(nft.MINTER_ROLE(), address(lpdaSale));

        vm.stopPrank();

        // simulate we wait until after the sale's end time
        vm.warp(endTime + 1 hours);

        vm.startPrank(buyer);

        // The following succeeds even if we are past the end time
        uint256 amount = 1;
        uint256 price = lpdaSale.getPrice();
        lpdaSale.buy{value: price * amount}(amount);

        vm.stopPrank();
    }
}

Recommendation

Check that the current block timestamp is before the sale's end time in the buy function:

 function buy(uint256 _amount) external payable {
         uint48 amount = uint48(_amount);
         Sale memory temp = sale;
         IEscher721 nft = IEscher721(temp.edition);
         require(block.timestamp >= temp.startTime, "TOO SOON");
+        require(block.timestamp < temp.endTime, "TOO LATE");
         ...