Summary
HealthTokens can be transferred between users which breaks invariant.
Details
As stated there should be only two ways to receive HealthToken:
These should be the two eligible ways to receive a HealthToken in this protocol.
setMarketAndVotingAddress: Allows the owner to set the addresses of the MartenitsaMarketplace and MartenitsaVoting contracts.
distributeHealthToken: Allows the marketplace and voting contracts to distribute HealthTokens as rewards to specified addresses.
However, the transfer
and transferFrom
allow another way to receive HealthTokens.
Proof of Code
paste this test and run it in MartenitsaMarketPlace.t.sol
function testCanTransferHealthTokens() public {
address pan = makeAddr("pan");
vm.startPrank(bob);
uint256 i = 0;
uint256 martenitsaToHealthTokenRatio = 3;
uint256 amountOfFreeMartenitsaTokens = 99;
for (i; i < amountOfFreeMartenitsaTokens; i++) {
martenitsaToken.updateCountMartenitsaTokensOwner(address(bob), "add");
}
marketplace.collectReward();
uint256 bobHealthTokensBalance = healthToken.balanceOf(address(bob));
uint256 expectedBobHealthTokensBalance = (amountOfFreeMartenitsaTokens / martenitsaToHealthTokenRatio) * 1e18;
assert(bobHealthTokensBalance == expectedBobHealthTokensBalance);
healthToken.transfer(pan, bobHealthTokensBalance);
vm.stopPrank();
console.log("Pan HealthTokens balance ", healthToken.balanceOf(pan));
assert(healthToken.balanceOf(pan) == bobHealthTokensBalance);
}
Tools Used
Manual Review
Unit tests
Recommendations
Override the default ERC20::transfer
and ERC20::transferFrom