Platform: Code4rena
Start Date: 07/03/2024
Pot Size: $63,000 USDC
Total HM: 20
Participants: 36
Period: 5 days
Judge: cccz
Total Solo HM: 11
Id: 349
League: BLAST
Rank: 3/36
Findings: 5
Award: $5,548.59
🌟 Selected for report: 3
🚀 Solo Findings: 2
🌟 Selected for report: DarkTower
2068.8771 USDC - $2,068.88
MagicLpAggregator
does not update the roundId
, startAt
, updatedAt
and answeredInRound
to correct values.
function latestRoundData() external view returns (uint80, int256, uint256, uint256, uint80) { return (0, latestAnswer(), 0, 0, 0); }
A common code is to check updatedAt
for staleness issue (although it isn't required to do so anymore.
(, int256 price, , uint256 updatedAt, ) = priceFeed.latestRoundData(); if (updatedAt < block.timestamp - 60 * 60 /* 1 hour */) { revert("stale price feed"); }
Therefore any integrator that uses the above code will not be able to integrate
MagicLpAggregator
oracles as it will always revert due to the incorrect updatedAt
being provided.
Manual Review
Use the values roundId
, startAt
, updatedAt
and answeredInRound
from whichever oracle, baseOracle
or quoteOracle
was used.
Other
#0 - c4-pre-sort
2024-03-15T13:12:01Z
141345 marked the issue as insufficient quality report
#1 - 141345
2024-03-15T13:12:03Z
invalid won't revert
#2 - c4-judge
2024-03-29T15:13:21Z
thereksfour marked the issue as unsatisfactory: Invalid
#3 - rexjoseph
2024-04-03T11:50:25Z
Hi again @thereksfour,
The lookout's response is incorrect:
invalid won't revert
I think we should reiterate the issue here as the submission has tried its best to point out:
MagicLpAggregator
gets the price from Chainlink's FeedMagicLPAggregator
Oracle for price specifically the latestRoundData()
function it exposes in their implementationupdatedAt
so they can be sure the feed is fresh to proceed with utilizing the returned dataupdatedAt
as well as other returned data are hardcoded to 0 in MagicLpAggregator
oracle implementation, the call reverts. 0 will always be less than block.timestamp - 60 * 60
The step by step description of the issue above I believe is sufficient but here's a provided POC to elaborate on this in code:
Test file in the abracadabra codebase is: MagicLPAggregator.t.sol
function testProtocolAIntegrationOfMagicLPAggReverts() public { // return values of `MagicLpAggregator` latestRoundData() (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) = aggregator.latestRoundData(); // protocol A tries to make sure the prices returned is fresh by checking the time // keep in mind the time aka updatedAt is hardcoded to 0. So 0 will always be less than `block.timestamp - 60 * 60` hence a revert if (updatedAt < block.timestamp - 60 * 60 /* 1 hour */) { revert("stale price feed"); } }
Ran 1 test for test/MagicLpAggregator.t.sol:MagicLpAggregatorTest [FAIL. Reason: revert: stale price feed] testProtocolAIntegrationOfMagicLPAggReverts() (gas: 57187) Suite result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 2.56s (2.88ms CPU time)
Thank you for taking another look at this!
#4 - thereksfour
2024-04-04T15:29:14Z
latestRoundData() is different from the standard and may cause integration issues. will consider it a valid M
#5 - c4-judge
2024-04-04T15:29:20Z
thereksfour marked the issue as primary issue
#6 - c4-judge
2024-04-04T15:29:26Z
thereksfour marked the issue as satisfactory
#7 - c4-judge
2024-04-04T15:29:37Z
thereksfour marked the issue as selected for report
🌟 Selected for report: ether_sky
Also found by: DarkTower, SpicyMeatball, hals
290.0407 USDC - $290.04
MagicLPAggregator
will return wrong price for tokens less than 18 decimals.
The MagicLPAggregator
intends to return price of a LP in terms of USD in 18 decimals.
function decimals() external pure override returns (uint8) { return 18; }
But let's take a look at the following function. MagicLpAggregator.sol#L37-L46
function latestAnswer() public view override returns (int256) { uint256 baseAnswerNomalized = uint256(baseOracle.latestAnswer()) * (10 ** (WAD - baseOracle.decimals())); uint256 quoteAnswerNormalized = uint256(quoteOracle.latestAnswer()) * (10 ** (WAD - quoteOracle.decimals())); uint256 minAnswer = baseAnswerNomalized < quoteAnswerNormalized ? baseAnswerNomalized : quoteAnswerNormalized; (uint256 baseReserve, uint256 quoteReserve) = _getReserves(); baseReserve = baseReserve * (10 ** (WAD - baseDecimals)); quoteReserve = quoteReserve * (10 ** (WAD - quoteDecimals)); return int256(minAnswer * (baseReserve + quoteReserve) / pair.totalSupply()); }
Let us break down the above function, it first:
baseOracle
quoteOracle
minAnswer
baseReserve + quoteReserve
of the MagicLPminAnswer * (baseReserve + quoteReserve) / pair.totalSupply()
But if you look at the above closely, you will notice that although the function seems correct it outputs the price in the wrong decimals (not 18):
We know that minAnswer
will always be the normalized oracle price to 18 decimals
(baseReserve + quoteReserve)
will always be the normalized token prices to 18 decimals
However, pair.totalSupply()
is not normalized, and depends on the decimals of the base token!
function decimals() public view override returns (uint8) { return IERC20Metadata(_BASE_TOKEN_).decimals(); }
Therefore if IERC20Metadata(_BASE_TOKEN_).decimals()
is not 18 decimals, let us say 6 decimals (ie. USDT token)
Than the decimals for the final output isn't correct as 18 + 18 - 6 = 30 decimals which is 10^12 times the intended price we wanted to return.
Manual Review
Normalize pair.totalSupply()
to 18 decimals so that the final output is 18 + 18 - 18 = 18 decimals.
Oracle
#0 - 0xm3rlin
2024-03-15T00:58:14Z
duplicate #191
#1 - c4-pre-sort
2024-03-15T13:01:17Z
141345 marked the issue as duplicate of #64
#2 - c4-judge
2024-03-29T15:21:38Z
thereksfour marked the issue as duplicate of #90
#3 - c4-judge
2024-03-29T16:52:43Z
thereksfour marked the issue as satisfactory
#4 - c4-judge
2024-03-29T17:04:52Z
thereksfour changed the severity to 2 (Med Risk)
🌟 Selected for report: DarkTower
2068.8771 USDC - $2,068.88
The Abracadabra team configures gas yield for all contracts in the Blast L2 except for the LockingMultiRewards
contract. Therefore, gas yields accrued on the LockingMultiRewards
staking contract since deployment will be lost as it was never configured using BlastYields.configureDefaultClaimables
. If the contract holds native yield reward tokens in ERC20 such as WETHRebasing or USDB then these rewards will also be lost.
During deployment of the staking contract on Blast, the staking contract's gas yield mode should be configured to be able to claim accrued gas fees spent on the contract. We know that the bootstrap()
function spawns the LockingMultiRewards
staking contract below.
function bootstrap(uint256 minAmountOut) external onlyOwner onlyState(State.Closed) returns (address, address, uint256) { ... @> staking = new LockingMultiRewards(pool, 30_000, 7 days, 13 weeks, address(this)); // spawns the staking contract ... }
However, in the LockingMultiRewards
contract, the BlastYields.configureDefaultClaimables
function does not exist in the LockingMultiRewards
constructor to configure the staking contract's gas yield mode. Therefore, gas yield cannot be claimed LockingMultiRewards
.
constructor( address _stakingToken, uint256 _lockingBoostMultiplerInBips, uint256 _rewardsDuration, uint256 _lockDuration, address _owner ) OperatableV2(_owner) { if (_lockingBoostMultiplerInBips <= BIPS) { revert ErrInvalidBoostMultiplier(); } if (_lockDuration < MIN_LOCK_DURATION) { revert ErrInvalidLockDuration(); } if (_rewardsDuration < MIN_REWARDS_DURATION) { revert ErrInvalidRewardDuration(); } if (_lockDuration % _rewardsDuration != 0) { revert ErrInvalidDurationRatio(); } stakingToken = _stakingToken; lockingBoostMultiplerInBips = _lockingBoostMultiplerInBips; rewardsDuration = _rewardsDuration; lockDuration = _lockDuration; // kocks are combined into the same `rewardsDuration` epoch. So, if // a user stake with locking every `rewardsDuration` this should reach the // maximum number of possible simultaneous because the first lock gets expired, // freeing up a slot. maxLocks = _lockDuration / _rewardsDuration; }
We believe this to be a mistake by the Abracadabra team as all other contracts that use substantial gas except for this contract had their gas yield mode configured/ The gas spent for this contract will be very substantial considering that users will spend substantial amounts of gas claiming from the staking contract. Therefore the Abracadabra is missing out the gas fees in this contract by not configuring the gas yield mode and possibly native yield in such as WETHRebasing or USDB if such tokens are used
Manual review + foundry
Configure the Blast gas yield mode for the LockingMultiRewards
staking contract in the constructor and expose a function for admin to collect the yields. Additionally, also consider whether Blast points need to be configured here.
Other
#0 - c4-pre-sort
2024-03-15T12:27:01Z
141345 marked the issue as duplicate of #229
#1 - c4-judge
2024-03-29T03:29:36Z
thereksfour marked the issue as unsatisfactory: Invalid
#2 - c4-judge
2024-03-29T04:51:18Z
thereksfour marked the issue as not a duplicate
#3 - c4-judge
2024-03-29T04:51:41Z
thereksfour marked the issue as satisfactory
#4 - c4-judge
2024-03-29T05:12:52Z
thereksfour marked the issue as primary issue
#5 - c4-judge
2024-03-31T07:09:22Z
thereksfour marked the issue as selected for report
#6 - trust1995
2024-04-02T17:04:04Z
The Abracadabra team is aware that not all contracts are wrapped with YIELD wrappers, from the README:
Some contracts were wrapped so it's usable with Blast L2 yield claiming
It is up to the team to decide which ones they are interested in claiming yield from. The suggestion made in the submission is very reasonable, but it belongs to Analysis / QA as a potential improvement, rather than a material handling of funds issue.
#7 - rexjoseph
2024-04-03T08:59:37Z
Hi again @thereksfour!
Thank you for seeing the issue here! Just to reiterate, I'll touch on points already present in our submission.
The Abracadabra team configures gas yield for all contracts in the Blast L2 except for the LockingMultiRewards contract.
The team has implemented a fix for this issue now and as we can see in FACTS as opposed to Trust's assessment, they do really care about the yield and native claiming system on Blast. The fix can be found here: https://github.com/Abracadabra-money/abracadabra-money-contracts/pull/134/commits/6cb4bf1cd60dd08b08b8ee488252ce2c0500775d
Therefore, gas yields accrued on the LockingMultiRewards staking contract since deployment will be lost as it was never configured using BlastYields.configureDefaultClaimables
To hit on this point further, it is important to understand that the LockingMultiRewards
contract will not ONLY be deployed on Arbitrum but also on Blast L2. Hence, yields on that contract will be a plenty ton as it will be one of the most used contracts within the Abracadabra protocol. You can see they now spawn up the LockingMultiRewards
correctly just as we suggested in their fix:
staking = new BlastLockingMultiRewards(registry, feeTo, pool, 30_000, 7 days, 13 weeks, address(this));
In any case, the provided link contains all of the fixes as highlighted by this report and as always, we thank you for the fast judgement! @thereksfour
#8 - trust1995
2024-04-03T11:14:46Z
The team has implemented a fix for this issue now and as we can see in FACTS as opposed to Trust's assessment, they do really care about the yield and native claiming system on Blast.
Objection, I never said they don't care about the yield. I stated it is a recommendation-level "issue" to point which additional contracts they should wrap.
To hit on this point further, it is important to understand that the
LockingMultiRewards
contract will not ONLY be deployed on Arbitrum but also on Blast L2.
This is a useless mention because if it wasn't the case, the entire premise would be invalid. Instead it is a valid suggestion.
The existence of a fix and view of the sponsor absolutely does not affect the severity level of the issue and is a cheap fallacy to abuse, quite frankly.
#9 - Haxatron
2024-04-03T13:22:34Z
Our rationale on this being a Medium is that the protocol has intended to made all the contracts which will generate significant gas yield (and token yield) claimable, and we believe this can be reasonably inferred from the repository. Therefore, if the protocol has missed out on making the gas yield and token yield from the LockingMultiRewards
claimable, we view that as a "leak of value" and would consider that as a Medium severity bug.
However, we also respect your viewpoint @Trust and we ask that the judge consider each viewpoint carefully before coming to a final decision. We shall not comment on this any further.
#10 - thereksfour
2024-04-04T15:20:02Z
I'll consider this an M about value leak.
930.9947 USDC - $930.99
The bootstrap process relies on users locking up all their tokens as it will only use tokens that are marked locked in totals[MIM].locked
and totals[USDB].locked
.
BlastOnboardingBoot.sol#L96-L127
function bootstrap(uint256 minAmountOut) external onlyOwner onlyState(State.Closed) returns (address, address, uint256) { if (pool != address(0)) { revert ErrAlreadyBootstrapped(); } => uint256 baseAmount = totals[MIM].locked; => uint256 quoteAmount = totals[USDB].locked; MIM.safeApprove(address(router), type(uint256).max); USDB.safeApprove(address(router), type(uint256).max); (pool, totalPoolShares) = router.createPool(MIM, USDB, FEE_RATE, I, K, address(this), baseAmount, quoteAmount); if (totalPoolShares < minAmountOut) { revert ErrInsufficientAmountOut(); } // Create staking contract // 3x boosting for locker, 7 days reward duration, 13 weeks lp locking // make this contract temporary the owner the set it as an operator // for permissionned `stakeFor` during the claiming process and then // transfer the ownership to the onboarding owner. staking = new LockingMultiRewards(pool, 30_000, 7 days, 13 weeks, address(this)); staking.setOperator(address(this), true); staking.transferOwnership(owner); // Approve staking contract pool.safeApprove(address(staking), totalPoolShares); emit LogLiquidityBootstrapped(pool, address(staking), totalPoolShares); return (pool, address(staking), totalPoolShares); }
However, it is possible that these values can be zero because there is a cap on the amount of tokens that can be stored in the contract.
function deposit(address token, uint256 amount, bool lock_) external whenNotPaused onlyState(State.Opened) onlySupportedTokens(token) { token.safeTransferFrom(msg.sender, address(this), amount); if (lock_) { totals[token].locked += amount; balances[msg.sender][token].locked += amount; } else { totals[token].unlocked += amount; balances[msg.sender][token].unlocked += amount; } totals[token].total += amount; if (caps[token] > 0 && totals[token].total > caps[token]) { revert ErrCapReached(); } balances[msg.sender][token].total += amount; emit LogDeposit(msg.sender, token, amount, lock_); }
In the code above, notice that there is a cap on the amount of tokens that can be sent to the contract caps[token]
. The problem here is that it is checked against the total token amount totals[token].total
rather than the locked amount totals[token].locked
.
Therefore a griefer can deposit tokens up to the caps[token]
with lock_ = false
. The result, is that no one else can deposit tokens into the contract.
During bootstrap, since these tokens are still considered unlocked, then totals[MIM].locked = 0
and totals[USDB].locked = 0
, therefore there won't be any locked tokens available for the bootstrapping process.
Manual Review
Instead of checking caps[token]
against totals[token].total
, it should be checked against totals[token].locked
.
Other
#0 - c4-pre-sort
2024-03-15T14:31:14Z
141345 marked the issue as sufficient quality report
#1 - 141345
2024-03-15T14:31:31Z
send cap amt
but the sender will lose a lot, and create another pool can solve it
#2 - c4-sponsor
2024-03-15T16:03:55Z
0xCalibur marked the issue as disagree with severity
#3 - 0xCalibur
2024-03-15T16:45:58Z
#4 - c4-sponsor
2024-03-15T16:46:10Z
0xCalibur (sponsor) acknowledged
#5 - c4-sponsor
2024-03-15T16:46:13Z
0xCalibur (sponsor) confirmed
#6 - thereksfour
2024-03-29T08:26:28Z
Incorrect cap check, consider M
#7 - c4-judge
2024-03-29T08:26:35Z
thereksfour changed the severity to 2 (Med Risk)
#8 - c4-judge
2024-03-29T16:10:08Z
thereksfour marked the issue as satisfactory
#9 - c4-judge
2024-03-31T07:10:53Z
thereksfour marked the issue as selected for report
#10 - trust1995
2024-04-02T17:29:45Z
I believe the finding is actually invalid:
In fact, the fix the team has applied here can now be abused by the first point to break the capacity invariant, by depositing with lock_=false, then calling lock(). @0xCalibur
This demonstrates the original code did not have a M-level issue, but is a design choice around how to properly limit the capacity.
#11 - rexjoseph
2024-04-03T08:40:11Z
Hi @thereksfour first off, thanks for the swift judgment ~ 24 hours is a great time to wrap this all up.
Now, I disagree with Trust's assessment of the issue here. As we already know, locked & unlocked tokens are different within the Abracadabra protocol. The locked tokens will facilitate the creation of MagicLP. The unlocked tokens on the other end will not.
The issue here is that the capacity for the locked tokens can and will be abused by users because anyone can deposit just as much as the amount of unlocked to grief the protocol of deposits and one way to fix this is for the protocol to bump up the cap. This is not an ideal fix in any way as the code would be unchanged but the issue persists. Hence, the recommendation to properly check caps against locked. Also, for the fact this state can be easily reached with one deposit from a malicious user, the MagicLP pool creation will not be possible. Downplaying this issue because you do not like the fix doesn't make the issue invalid in any way.
On the other hand, DoS/grief of a protocol function is considered top-level MEDIUM and in some cases HIGH in c4. And following that, In essence, this submission also satisfies all of that severity categorization.
Thank you for taking the time to go through this and seeing the issue here as the sponsor has already confirmed.
#12 - trust1995
2024-04-03T11:08:53Z
My argument is that there is a trade-off at play here and both options have their weaknesses but that does not make it an M level issue, but rather a design opinion.
The issue here is that the capacity for the locked tokens can and will be abused by users False assumption that users will "abuse" the protocol by depositing tokens for zero yield.
Capacity can always be lifted beyond such "grief" amounts being sent.
I respect your view, please respect mine and let the judge make the correct call.
#13 - Haxatron
2024-04-03T13:24:59Z
Our final thoughts on this is that we believe that this is not an intended design choice and not a tradeoff the protocol accepts because the sponsor has marked this as confirmed and fixed. Edit: And to add on we do not believe adjusting the cap is a mitigation to this because the protocol will miss out on tokens that users intended to be locked while capacity is reached.
We ask the judge to review each of our viewpoints here carefully before coming to a conclusion. We shall not comment on this any further.
#14 - 0xCalibur
2024-04-04T13:28:29Z
I believe the finding is actually invalid:
- The cap is checking for total tokens because at any point, a token that is unlocked can become locked through the lock() function. Therefore, we treat all tokens deposits as "potentially locked" tokens, and prepare a perimeter around that.
- Griefer can deposit their tokens in the contract for zero-yield, that's a tradeoff the protocol seems to be fine to live with.
In fact, the fix the team has applied here can now be abused by the first point to break the capacity invariant, by depositing with lock_=false, then calling lock(). @0xCalibur
This demonstrates the original code did not have a M-level issue, but is a design choice around how to properly limit the capacity.
True, the current fix doesn't resolve the issue. Here is the new mitigation: https://github.com/Abracadabra-money/abracadabra-money-contracts/pull/161
#15 - thereksfour
2024-04-04T15:06:33Z
I would still think it's a valid M. The purpose of the protocol is to have more locked tokens, and obviously in this issue wavering users and griefers may block users who want to lock.
#16 - c4-judge
2024-04-04T15:06:39Z
thereksfour marked the issue as primary issue
🌟 Selected for report: ether_sky
Also found by: 0x11singh99, 0xE1, 0xJaeger, Bauchibred, Bigsam, Bozho, Breeje, DarkTower, HChang26, SpicyMeatball, Trust, ZanyBonzy, albahaca, bareli, blutorque, grearlake, hals, hassan-truscova, hihen, oualidpro, pfapostol, ravikiranweb3, slvDev, zhaojie
189.8 USDC - $189.80
BlastOnboarding
.Due to the onlySupportedTokens(token)
modifier, if the owner removes support for tokens, users can no longer withdraw their tokens and their funds will get stuck in the BlastOnboarding
.
function withdraw(address token, uint256 amount) external whenNotPaused onlySupportedTokens(token) { balances[msg.sender][token].unlocked -= amount; balances[msg.sender][token].total -= amount; totals[token].unlocked -= amount; totals[token].total -= amount; token.safeTransfer(msg.sender, amount); emit LogWithdraw(msg.sender, token, amount); }
Rescues will also not be able function for unsupported tokens.
function rescue(address token, address to, uint256 amount) external onlyOwner { if (supportedTokens[token]) { revert ErrNotAllowed(); } token.safeTransfer(to, amount); emit LogTokenRescue(token, to, amount); }
Therefore, these tokens will get stuck in the contract until it supports these tokens again which may not be ideal.
Consider removing onlySupportedTokens(token)
modifier for withdrawals and rescues
FeeRateModel.sol
, FeeRateModelImpl.sol
, BlastTokenRegistry.sol
do not configure gas yield.The constructors for the following contracts do not configure gas yield on Blast:
Gas yield for these files won't be substantial so this is marked as low.
BlastMagicLP
implementation do not configure gas yield.The implementation BlastMagicLP
does not configure the gas yield mode in the constructor.
constructor(BlastTokenRegistry registry_, address feeTo_, address owner_) MagicLP(owner_) { if (feeTo_ == address(0)) { revert ErrZeroAddress(); } if (address(registry_) == address(0)) { revert ErrZeroAddress(); } registry = registry_; feeTo = feeTo_; }
Since there will still be operations carried out on the implementation contract for admin configuration purposes, it might be best to configure the yield mode by calling BlastYields.configureDefaultClaimables(address(this))
in the constructor so that any gas spent on implementation contract can be reclaimed.
The maintainer fee model can be changed in the Factory for new MagicLPs deployed.
function setMaintainerFeeRateModel(IFeeRateModel maintainerFeeRateModel_) external onlyOwner { if (address(maintainerFeeRateModel_) == address(0)) { revert ErrZeroAddress(); } maintainerFeeRateModel = maintainerFeeRateModel_; emit LogSetMaintainerFeeRateModel(maintainerFeeRateModel_); }
However, this affects new pools deployed and old pools will still use the old maintainer fee model leading to different pools potentially having different fee models which breaks consistency. The maintainer fee rate model contract already has a function to change the rate model implementation and therefore such a function in the factory isn't required.
/// @notice Set the fee rate implementation and default fee rate /// @param implementation_ The address of the fee rate implementation, use address(0) to disable function setImplementation(address implementation_) public onlyOwner { implementation = implementation_; emit LogImplementationChanged(implementation_); }
MagicLP is neither pausable nor upgradeable (it uses non-upgradeable clones). We don't believe this to be a good idea because if a pool-draining exploit was found it would be nearly impossible to protect all the funds still present in the MagicLP.
For all swap functions in the Router, it doesn't validate that the lp
address provided is an actual MagicLP. While we couldn't find a way to exploit this. Historically, this has been the cause of famous router hacks such as the famous Sushiswap hack - https://maxwelldulin.com/BlogPost/sushiswap-exploit-explained-2023
MagicLpAggregator
can't work with tokens with more than 18 decimals or if any token oracle returns more than 18 decimalsIt will revert if any token oracle returns more than 18 decimals due to underflow in WAD - baseOracle.decimals()
or WAD - quoteOracle.decimals()
.
uint256 baseAnswerNomalized = uint256(baseOracle.latestAnswer()) * (10 ** (WAD - baseOracle.decimals())); uint256 quoteAnswerNormalized = uint256(quoteOracle.latestAnswer()) * (10 ** (WAD - quoteOracle.decimals()));
It can also revert for tokens with more than 18 decimals due to underflow in (WAD - baseDecimals)
or (WAD - quoteDecimals)
.
baseReserve = baseReserve * (10 ** (WAD - baseDecimals)); quoteReserve = quoteReserve * (10 ** (WAD - quoteDecimals));
The BIPS
variable caps the maximum boost multiplier at 100%. It could do well to be renamed to MAX_BIPS
for better code readability.
- uint256 private constant BIPS = 10_000; + uint256 private constant MAX_BIPS = 10_000;
_udpateUserRewards
function namingAs can be seen from the title of this log and in the codebase, the function to update user rewards has a typo and should be corrected.
function _udpateUserRewards(address user_, uint256 balance_, address token_, uint256 rewardPerToken_) internal { rewards[user_][token_] = _earned(user_, balance_, token_, rewardPerToken_); userRewardPerTokenPaid[user_][token_] = rewardPerToken_; }
- function _udpateUserRewards(address user_, uint256 balance_, address token_, uint256 rewardPerToken_) internal { + function _updateUserRewards(address user_, uint256 balance_, address token_, uint256 rewardPerToken_) internal { rewards[user_][token_] = _earned(user_, balance_, token_, rewardPerToken_); userRewardPerTokenPaid[user_][token_] = rewardPerToken_; }
#0 - c4-pre-sort
2024-03-15T15:00:47Z
141345 marked the issue as sufficient quality report
#1 - 141345
2024-03-16T01:08:02Z
9 DarkTower l r nc 2 1 2
L 1 d dup of https://github.com/code-423n4/2024-03-abracadabra-money-findings/issues/95 L 2 d dup of https://github.com/code-423n4/2024-03-abracadabra-money-findings/issues/229 L 3 i L 4 l L 5 r L 6 l L 7 d dup of https://github.com/code-423n4/2024-03-abracadabra-money-findings/issues/88 R 1 n
NC 1 n
#2 - c4-judge
2024-03-29T16:32:54Z
thereksfour marked the issue as grade-b
#3 - rexjoseph
2024-04-03T10:01:30Z
Hi @thereksfour, I believe [L-06] is a duplicate of #86.
Also, looking at this report's value and comparing it to other LLM reports, for example, #110, #112, & #54, I believe this report provides more value than the above-mentioned QA reports and should be up there as grade-a. Nothing against the other reports frankly, and this is up to you as I believe this report should be re-evaluated.
Thanks!
#4 - c4-judge
2024-04-06T06:49:04Z
thereksfour marked the issue as grade-a