Summary
MartenitsaMarketplace::listMartenitsaForSale
does not have a cap on the price of the martenitsa that can be listed for sale, This can be exploited to list martenitsa for sale at an exorbitant price for the users.
Vulnerability Details
When a Producer lists a martenitsa for sale, they can set the price of the martenitsa. There is no check to ensure that the price of the martenitsa is reasonable. This can be exploited by a malicious Producer to list a martenitsa for sale at an exorbitant price.
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 0");
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);
}
POC
Add this test to MartenitsaMarketplace.test.sol
:
function testListMartenitsaForSalePOC_HighPrice() public createMartenitsa {
vm.prank(chasy);
marketplace.listMartenitsaForSale(0, 1_200 ether);
list = marketplace.getListing(0);
assert(list.seller == chasy);
assert(list.price == 1_200 ether);
}
Test output:
Ran 1 test for test/MartenitsaMarketplace.t.sol:MartenitsaMarketplace
[PASS] testListMartenitsaForSalePOC_HighPrice() (gas: 332503)
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 6.92ms (394.25µs CPU time)
When the Producer chasy
lists a martenitsa for sale at a price of 1_200 ether
, the transaction is successful. This can be exploited by a malicious Producer to list a martenitsa for sale at an exorbitant price.
Impact
A malicious Producer can list a martenitsa for sale at an exorbitant price, which can be detrimental to the users.
Tools Used
Foundry and Manual review.
Recommendations
Add a cap on the price (Maximum price of a martenitsa token) of the martenitsa that can be listed for sale.
We add a state variable maxPrice
to the contract and set it to a reasonable value. We then add a check in the listMartenitsaForSale
function to ensure that the price of the martenitsa being listed for sale is less than or equal to maxPrice
. Example: 1 ETH as the maximum price.
+ uint256 public maxPrice = 1e18; // 1 ETH
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 0");
+ require(price <= maxPrice, "Price is too high");
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);
}