-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Reward accounting is incorrect in BathBuddy contract #1279
Comments
0xSorryNotSorry marked the issue as high quality report |
0xSorryNotSorry marked the issue as primary issue |
daoio marked the issue as sponsor confirmed |
HickupHH3 marked the issue as selected for report |
This write-up encapsulates the issues arising from forking the staking contract, but doing away with the staking portion through the usage of
|
I think that in duplicate, two categories of issues have been merged into one. So the two separate problems that have been merged into one are:
Both are different issues and require different solutions. Just for example, two marked duplicates are : And and each explain separate issue of uneven distribution and draining. |
(2) is enabled by (1). The removal of staking & withdrawing (1) also led to the removal of initialisation of the |
Lines of code
https://github.com/code-423n4/2023-04-rubicon/blob/main/contracts/periphery/BathBuddy.sol#L38
Vulnerability details
Impact
The
BathBuddy
contracts implements rewards for liquidity providers (holders ofBathToken
). The contract is modeled after the famous Synthetix staking contract, with some tweaks to support rewards for multiple tokens at the same time.The implementation overall is correct, however there is a critical difference with the Synthetix contract that is ignored in the
BathBuddy
contract. In the Synthetix implementation the main actions related to rewards accounting are thestake
andwithdraw
actions, these trigger theupdateReward
modifier to ensure correct reward accounting. Staked tokens cannot be transferred, as these are held in the staking contract. In the BathBuddy implementation things are very different as there is no staking, rewards are intended to be distributed directly to holders of the BathToken without any need of staking the tokens in the contract. This means that, as there is no "staking" action in the BathBuddy implementation (i.e. depositing funds in the contract), rewards fail to be correctly accounted whenever BathToken are minted, burned or transferred between different accounts.These are two critical places in the code where the BathBuddy contract uses the state from the BathToken, but fails to be triggered whenever the state in the BathToken is modified. The first is the
rewardPerToken
that calculates the amount of rewards that should correspond to one unit of the BathToken token, this is logically dependent on the total supply of the token (lines 124 and 133):https://github.com/code-423n4/2023-04-rubicon/blob/main/contracts/periphery/BathBuddy.sol#L121-L135
The other place is in the
earned
function which uses the BathTokenbalanceOf
function of an account (lines 146-147):https://github.com/code-423n4/2023-04-rubicon/blob/main/contracts/periphery/BathBuddy.sol#L139-L161
Since the whole BathBuddy contract is dependent on the total supply and account balance state of the paired BathToken contract, the following actions in the token should update the rewards state in BathBuddy:
mint
andburn
, as these modify the total supply of the token and the balances of the account whose tokens are minted or burned.transfer
andtransferFrom
, as these modify the balances of the sender and recipient accounts.As the BathBuddy
updateReward
modifier fails to be triggered when the mentioned state in the BathToken is modified, reward accounting will be incorrect for many different scenarios. We'll explore one of these in the next section.Proof of Concept
In the following test we demonstrate one of the possible scenarios where reward accounting is broken, this is a simple case in which rewards fail to be updated when a token transfer is executed. Alice has 1e18 BathTokens, at the middle of the rewards duration period she sends all her tokens to Bob. The expected outcome should be that Alice would earn half of the rewards, as she held the tokens for the half of the duration period. But when the duration period has ended, we call
getReward
for both Alice and Bob and we can see that Alice got nothing and Bob earned 100% of the rewards.Note: the snippet shows only the relevant code for the test. Full test file can be found here.
Recommendation
There are two recommended paths here. The easy path would be to just add the
stake
andwithdraw
functions to the BathBuddy contract similar to how the original StakingRewards contract works on Synthetix. However, this may change the original intention of the protocol as rewards won't be earned just by holding BathTokens, they will need to be staked (rewards will only be distributed to stakers).The other path, and a bit more complex, would be to modify the BathToken contract (the cToken) so that burn, mint and transfer actions trigger the update on the paired BathBuddy contract.
The text was updated successfully, but these errors were encountered: