If the saleReceiver in LPDA.sol is a contract address without receive() or fallback() functions, ethers will be locked. saleReceiver is not updatable.
The feeReceiver in LPDAFactory.sol, FixedPriceFactory.sol and OpenEditionFactory.sol is also affected, but there is a setter for this address and the user can change it with another one.
Proof of Concept
Test case
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import "forge-std/Test.sol";
import {EscherTest} from "./utils/EscherTest.sol";
import {LPDAFactory, LPDA} from "src/minters/LPDAFactory.sol";
contract SaleReceiver {
// empty
}
contract LPDAPush is EscherTest {
LPDAFactory public lpdaSales;
LPDA.Sale public lpdaSale;
SaleReceiver public saleReceiver;
LPDA public sale;
function setUp() public virtual override {
saleReceiver = new SaleReceiver();
super.setUp();
lpdaSales = new LPDAFactory();
// set up a LPDA Sale
lpdaSale = LPDA.Sale({
currentId: uint48(0),
finalId: uint48(10),
edition: address(edition),
startPrice: uint80(uint256(1 ether)),
finalPrice: uint80(uint256(0.1 ether)),
dropPerSecond: uint80(uint256(0.1 ether) / 1 days),
startTime: uint96(block.timestamp),
saleReceiver: payable(address(saleReceiver)),
endTime: uint96(block.timestamp + 1 days)
});
}
function test_TransferToContractWithoutReceive() public {
sale = LPDA(lpdaSales.createLPDASale(lpdaSale));
// authorize the lpda sale to mint tokens
edition.grantRole(edition.MINTER_ROLE(), address(sale));
sale.buy{value: 1 ether}(1);
assertEq(address(sale).balance, 1 ether);
vm.warp(block.timestamp + 1 days);
assertApproxEqRel(sale.getPrice(), 0.9 ether, lpdaSale.dropPerSecond);
// buy the rest
// this will auto end the sale
sale.buy{value: uint256((0.9 ether + lpdaSale.dropPerSecond) * 9)}(9);
}
}
The recommended way to is to use pull over push pattern. Reference: ConsenSys Development Recommendations.
If the pull over push pattern will not be used, there should be a update function for the saleReceiver.
Lines of code
https://github.com/code-423n4/2022-12-escher/blob/main/src/minters/LPDA.sol#L86
Vulnerability details
Impact
If the
saleReceiver
inLPDA.sol
is a contract address withoutreceive()
orfallback()
functions, ethers will be locked.saleReceiver
is not updatable.The
feeReceiver
inLPDAFactory.sol
,FixedPriceFactory.sol
andOpenEditionFactory.sol
is also affected, but there is a setter for this address and the user can change it with another one.Proof of Concept
Test case
Failing part of the test result
Tools Used
Foundry
Recommended Mitigation Steps
The recommended way to is to use pull over push pattern. Reference: ConsenSys Development Recommendations. If the pull over push pattern will not be used, there should be a update function for the
saleReceiver
.