Summary
The file SecurityChecklist.md inside the test/security folder mentions the following invariant: Reward per token can't exceed actual added reward. This is not always the case, since in the below PoC the invariant breaks.
Vulnerability Details
Add the following code in stakeUnstake.t.sol file:
function test_PoC_SecurityChecklist() public {
uint256 epochDuration = 86_400 * 7;
vm.prank(alice);
fjordStaking.stake(1 ether);
skip(epochDuration);
vm.prank(minter);
uint256 rewardsEpoch1 = 1 ether;
fjordStaking.addReward(rewardsEpoch1);
console.log("currentEpoch :", fjordStaking.currentEpoch());
console.log("rewardPerToken[epoch 1] :", fjordStaking.rewardPerToken(1));
console.log("rewardPerToken[epoch 2] :", fjordStaking.rewardPerToken(2));
skip(epochDuration);
vm.prank(minter);
uint256 rewardsEpoch2 = 1 ether;
fjordStaking.addReward(rewardsEpoch2);
console.log("currentEpoch :", fjordStaking.currentEpoch());
console.log("rewardPerToken[epoch 1] :", fjordStaking.rewardPerToken(1));
console.log("rewardPerToken[epoch 2] :", fjordStaking.rewardPerToken(2));
assertLe(fjordStaking.rewardPerToken(2), rewardsEpoch2);
}
The output of the above test is the following:
[FAIL. Reason: assertion failed: 2000000000000000000 > 1000000000000000000] test_PoC_SecurityChecklist() (gas: 352785)
Logs:
currentEpoch : 2
rewardPerToken[epoch 1] : 0
rewardPerToken[epoch 2] : 0
currentEpoch : 3
rewardPerToken[epoch 1] : 0
rewardPerToken[epoch 2] : 2000000000000000000
Traces:
[352785] StakeUnstakeScenarios::test_PoC_SecurityChecklist()
....
Suite result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 764.10ms (10.66ms CPU time)
Ran 2 test suites in 1.37s (1.52s CPU time): 3 tests passed, 1 failed, 0 skipped (4 total tests)
Failing tests:
Encountered 1 failing test in test/integration/stakeUnstake.t.sol:StakeUnstakeScenarios
[FAIL. Reason: assertion failed: 2000000000000000000 > 1000000000000000000] test_PoC_SecurityChecklist() (gas: 352785)
The above test is intended to fail, showing that the invariant fails to be always true.
Impact
Invariant breaks
Tools Used
Manual Review
Recommendations
Implement additional checks, so that there is no scenario that this invariant breaks.