Beginner FriendlyFoundryGameFi
100 EXP
View results
Submission Details
Severity: high
Valid

Wrong calculation of `amountsRewards` in `callReward` function.

Summary

callReward function does not properly calculate the amountRewards of the HealthToken based on the MartenitsaTokens of users at that time. It should allow users to get 1 HelthToken every 3 new MartenitsaTokens but it does not take into account that the CountMartenitsaTokensOwner can change making or receiving MartenitsaTokens using the makePresent function.

Vulnerability Details

The calculation of the amountRewards in the codebase does not differentiate the new MartenitsaTokens from the old ones.

uint256 amountRewards = (count / requiredMartenitsaTokens) - _collectedRewards[msg.sender];
if (amountRewards > 0) {
_collectedRewards[msg.sender] = amountRewards;
healthToken.distributeHealthToken(msg.sender, amountRewards);
}

Impact

Considering a situation in which:

Bob buys 6 MartenitsaTokens and got 2 HealthTokens’ using the callRewardfunction. Then using themakePresentfunction, he transfers 3MartenisaTokensto Jack, and receives using the same function, 3 other newMartenisaTokensfrom Sam. Then Bob should be able to use again the functioncallRewardto get 1 moreHealthTokenfor the 3 newMartenisaTokens` but he can not get any additional token.

Run the test using command: forge test --match-path test/MartenitsaMarketplace.t.sol --match-test testReward -vvvv

function testReward() public listMartenitsa {
vm.startPrank(jack);
martenitsaToken.createMartenitsa("good1");
martenitsaToken.createMartenitsa("good2");
martenitsaToken.createMartenitsa("good3");
martenitsaToken.createMartenitsa("good4");
martenitsaToken.createMartenitsa("good5");
marketplace.listMartenitsaForSale(1, 1 wei);
marketplace.listMartenitsaForSale(2, 1 wei);
marketplace.listMartenitsaForSale(3, 1 wei);
marketplace.listMartenitsaForSale(4, 1 wei);
marketplace.listMartenitsaForSale(5, 1 wei);
martenitsaToken.setApprovalForAll(address(marketplace), true);
vm.stopPrank();
vm.startPrank(chasy);
martenitsaToken.createMartenitsa("good6");
martenitsaToken.createMartenitsa("good7");
martenitsaToken.createMartenitsa("good8");
marketplace.listMartenitsaForSale(6, 1 wei);
marketplace.listMartenitsaForSale(7, 1 wei);
marketplace.listMartenitsaForSale(8, 1 wei);
martenitsaToken.setApprovalForAll(address(marketplace), true);
vm.stopPrank();
vm.startPrank(bob);
marketplace.buyMartenitsa{value: 1 wei}(0);
marketplace.buyMartenitsa{value: 1 wei}(1);
marketplace.buyMartenitsa{value: 1 wei}(2);
marketplace.buyMartenitsa{value: 1 wei}(3);
marketplace.buyMartenitsa{value: 1 wei}(4);
marketplace.buyMartenitsa{value: 1 wei}(5);
marketplace.collectReward();
martenitsaToken.setApprovalForAll(address(marketplace), true);
marketplace.makePresent(jack, 3);
marketplace.makePresent(jack, 4);
marketplace.makePresent(jack, 5);
vm.stopPrank();
vm.startPrank(sam);
marketplace.buyMartenitsa{value: 1 wei}(6);
marketplace.buyMartenitsa{value: 1 wei}(7);
marketplace.buyMartenitsa{value: 1 wei}(8);
marketplace.collectReward();
martenitsaToken.setApprovalForAll(address(marketplace), true);
marketplace.makePresent(bob, 6);
marketplace.makePresent(bob, 7);
marketplace.makePresent(bob, 8);
vm.stopPrank();
vm.startPrank(bob);
marketplace.collectReward();
assert(healthToken.balanceOf(bob) == 2 * 10 ** 18);
vm.stopPrank();
}

and you get the following result:

├─ [0] VM::stopPrank()
│ └─ ← ()
├─ [0] VM::startPrank(bob: [0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e])
│ └─ ← ()
├─ [2889] MartenitsaMarketplace::collectReward()
│ ├─ [630] MartenitsaToken::isProducer(bob: [0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e]) [staticcall]
│ │ └─ ← false
│ ├─ [627] MartenitsaToken::getCountMartenitsaTokensOwner(bob: [0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e]) [staticcall]
│ │ └─ ← 6
│ └─ ← ()
├─ [629] HealthToken::balanceOf(bob: [0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e]) [staticcall]
│ └─ ← 2000000000000000000 [2e18]
├─ [0] VM::stopPrank()
│ └─ ← ()
└─ ← ()
Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 3.15ms
Ran 1 test suite in 3.15ms: 1 tests passed, 0 failed, 0 skipped (1 total tests)

Tools Used

Foundry

Recommendations

Make these changes to the codebase:

mapping(address => uint256) private _collectedRewards;
mapping(uint256 => Listing) public tokenIdToListing;
+ mapping(address => mapping(uint256 => bool)) public IdTokenusedforrewards;
function collectReward() external {
require(!martenitsaToken.isProducer(msg.sender), "You are producer and not eligible for a reward!");
uint256 count = martenitsaToken.getCountMartenitsaTokensOwner(msg.sender);
- uint256 amountRewards = (count / requiredMartenitsaTokens) - _collectedRewards[msg.sender];
- if (amountRewards > 0) {
- _collectedRewards[msg.sender] = amountRewards;
- healthToken.distributeHealthToken(msg.sender, amountRewards);
- }
- }
+ uint256 nexttokenid = martenitsaToken.getNextTokenId();
+ for (uint256 i=0; i<nexttokenid; ++i){
+ if (msg.sender == martenitsaToken.ownerOf(i) && IdTokenusedforrewards[msg.sender][i] == true){
+ count -= 1;
+ }
+ else if (msg.sender == martenitsaToken.ownerOf(i) && IdTokenusedforrewards[msg.sender][i] == false){
+ IdTokenusedforrewards[msg.sender][i] = true;
+ }
+ }
+ uint256 amountRewards = (count / requiredMartenitsaTokens);
+ healthToken.distributeHealthToken(msg.sender, amountRewards);
+ }
Updates

Lead Judging Commences

bube Lead Judge over 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

Incorrect logic in collectReward

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.