Summary
MartenitsaMarketplace::buyMartenitsa
should always fail because the Producer has not approved the Marketplace contract to transfer the martenitsa token on their behalf. The producers can know the exact moment when the martenitsa token is sold for them to approve the transfer.
Vulnerability Details
Whenever a buyer calls the MartenitsaMarketplace::buyMartenitsa
function, because the Producer has not approved the Marketplace contract to transfer the martenitsa token on their behalf, the transaction should always fail. This is because the Marketplace contract does not have the required allowance to transfer the martenitsa token on behalf of the Producer. There's no approval in the Martenitsa::listMartenitsaForSale
function.
function buyMartenitsa(uint256 tokenId) external payable {
Listing memory listing = tokenIdToListing[tokenId];
require(listing.forSale, "Token is not listed for sale");
require(msg.value >= listing.price, "Insufficient funds");
address seller = listing.seller;
address buyer = msg.sender;
uint256 salePrice = listing.price;
martenitsaToken.updateCountMartenitsaTokensOwner(buyer, "add");
martenitsaToken.updateCountMartenitsaTokensOwner(seller, "sub");
delete tokenIdToListing[tokenId];
emit MartenitsaSold(tokenId, buyer, salePrice);
(bool sent, ) = seller.call{value: salePrice}("");
require(sent, "Failed to send Ether");
@> martenitsaToken.safeTransferFrom(seller, buyer, tokenId);
}
POC
Add this test to `MartenitsaMarketplace.test.sol`:
function testBuyMartenitsaFailNoApproval() public listMartenitsa {
vm.prank(bob);
marketplace.buyMartenitsa{value: 1 wei}(0);
}
then run forge test --mt testBuyMartenitsaFailNoApproval and the result should FAIL:
Encountered 1 failing test in test/MartenitsaMarketplace.t.sol:MartenitsaMarketplace
[FAIL. Reason: ERC721InsufficientApproval(0xF62849F9A0B5Bf2913b396098F7c7019b51A820a, 0)] testBuyMartenitsaFailNoApproval() (gas: 314904)
Impact
A potential buyer can never buy a martenitsa token from the Marketplace contract because the Producer has not approved the Marketplace contract to transfer the martenitsa token on their behalf.
Tools Used
Foundry and Manual review.
Recommendations
The Producer should approve the Marketplace contract to transfer the martenitsa token on their behalf before listing the martenitsa token for sale.
add the following line to the listMartenitsaForSale
function:
function listMartenitsaForSale(uint256 tokenId, uint256 price) external {
require(msg.sender == martenitsaToken.ownerOf(tokenId), "You do not own this token");
require(martenitsaToken.isProducer(msg.sender), "You are not a producer!");
require(price > 0, "Price must be greater than zero");
Listing memory newListing = Listing({
tokenId: tokenId,
seller: msg.sender,
price: price,
design: martenitsaToken.getDesign(tokenId),
forSale: true
});
tokenIdToListing[tokenId] = newListing;
emit MartenitsaListed(tokenId, msg.sender, price);
+ martenitsaToken.approve(address(this), tokenId);
}
then run forge test --mt testBuyMartenitsaFailNoApproval and the result should PASS:
Ran 1 test for test/MartenitsaMarketplace.t.sol:MartenitsaMarketplace
[PASS] testBuyMartenitsaFailNoApproval() (gas: 314904)