Platform: Code4rena
Start Date: 30/11/2021
Pot Size: $100,000 USDC
Total HM: 15
Participants: 36
Period: 7 days
Judge: 0xean
Total Solo HM: 4
Id: 62
League: ETH
Rank: 1/36
Findings: 4
Award: $11,111.38
π Selected for report: 2
π Solo Findings: 1
hack3r-0m
https://github.com/code-423n4/2021-11-streaming/blob/main/Streaming/src/Locke.sol#L795-L803
createStream
does not check if deposit token and reward token are different addresses.
Not Required
Manual Review
add check require(rewardToken != depositToken)
#0 - 0xean
2022-01-14T21:04:00Z
dupe of #215
hack3r-0m
https://github.com/code-423n4/2021-11-streaming/blob/main/Streaming/src/Locke.sol#L733-L751
max approval is very common since one does not want to approve many times due to high gas fees.
Manual Review
add a mapping to keep track of whether incentive of particular token was claimed by creator and set it to true. In arbitraryCall, that mapping[who] should be false proving nobody has incentivised that token before.
#0 - 0xean
2022-01-14T21:40:48Z
dupe of #258
π Selected for report: hack3r-0m
8058.1521 USDC - $8,058.15
hack3r-0m
https://github.com/code-423n4/2021-11-streaming/blob/main/Streaming/src/Locke.sol#L229
reverts due to overflow for higher values (but strictly less than type(uint112).max) and hence when user calls exit
or withdraw
function it will revert and that user will not able to withdraw funds permanentaly.
Attaching diff to modify tests to reproduce behaviour:
diff --git a/Streaming/src/test/Locke.t.sol b/Streaming/src/test/Locke.t.sol index 2be8db0..aba19ce 100644 --- a/Streaming/src/test/Locke.t.sol +++ b/Streaming/src/test/Locke.t.sol @@ -166,14 +166,14 @@ contract StreamTest is LockeTest { ); testTokenA.approve(address(stream), type(uint256).max); - stream.fundStream((10**14)*10**18); + stream.fundStream(1000); - alice.doStake(stream, address(testTokenB), (10**13)*10**18); + alice.doStake(stream, address(testTokenB), 100); hevm.warp(startTime + minStreamDuration / 2); // move to half done - bob.doStake(stream, address(testTokenB), (10**13)*10**18); + bob.doStake(stream, address(testTokenB), 100); hevm.warp(startTime + minStreamDuration / 2 + minStreamDuration / 10); @@ -182,10 +182,10 @@ contract StreamTest is LockeTest { hevm.warp(startTime + minStreamDuration + 1); // warp to end of stream - // alice.doClaimReward(stream); - // assertEq(testTokenA.balanceOf(address(alice)), 533*(10**15)); - // bob.doClaimReward(stream); - // assertEq(testTokenA.balanceOf(address(bob)), 466*(10**15)); + alice.doClaimReward(stream); + assertEq(testTokenA.balanceOf(address(alice)), 533); + bob.doClaimReward(stream); + assertEq(testTokenA.balanceOf(address(bob)), 466); } function test_stake() public { diff --git a/Streaming/src/test/utils/LockeTest.sol b/Streaming/src/test/utils/LockeTest.sol index eb38060..a479875 100644 --- a/Streaming/src/test/utils/LockeTest.sol +++ b/Streaming/src/test/utils/LockeTest.sol @@ -90,11 +90,11 @@ abstract contract LockeTest is TestHelpers { testTokenA = ERC20(address(new TestToken("Test Token A", "TTA", 18))); testTokenB = ERC20(address(new TestToken("Test Token B", "TTB", 18))); testTokenC = ERC20(address(new TestToken("Test Token C", "TTC", 18))); - write_balanceOf_ts(address(testTokenA), address(this), (10**14)*10**18); - write_balanceOf_ts(address(testTokenB), address(this), (10**14)*10**18); - write_balanceOf_ts(address(testTokenC), address(this), (10**14)*10**18); - assertEq(testTokenA.balanceOf(address(this)), (10**14)*10**18); - assertEq(testTokenB.balanceOf(address(this)), (10**14)*10**18); + write_balanceOf_ts(address(testTokenA), address(this), 100*10**18); + write_balanceOf_ts(address(testTokenB), address(this), 100*10**18); + write_balanceOf_ts(address(testTokenC), address(this), 100*10**18); + assertEq(testTokenA.balanceOf(address(this)), 100*10**18); + assertEq(testTokenB.balanceOf(address(this)), 100*10**18); defaultStreamFactory = new StreamFactory(address(this), address(this));
Manual Review
Consider doing arithmetic operations in two steps or upcasting to u256 and then downcasting. Alternatively, find a threshold where it breaks and add require condition to not allow total stake per user greater than threshhold.
#0 - itsmetechjay
2021-12-08T14:51:04Z
Per the warden's request, I added this sentence to the mitigation steps: "Alternatively, find a threshold where it breaks and add require condition to not allow total stake per user greater than threshhold."
π Selected for report: hack3r-0m
Also found by: Jujic, toastedsteaksandwich
217.5701 USDC - $217.57
hack3r-0m
https://github.com/code-423n4/2021-11-streaming/blob/main/Streaming/src/Locke.sol#L397
https://github.com/code-423n4/2021-11-streaming/blob/main/Streaming/src/Locke.sol#L711
Due to integer divison of solidity, (999 * 10) / 10000 = 0, so for all values fee * amount
< 10000, user does not have to pay if fee is enabled or fee during executing flashloan.
A user can deposit total amount into chunks such that on every fund / flashloan, user does not have to pay fee.
Similiary, (1999 * 10) / 10000 = 1 can be exploited.
(included above)
Manual Review
Use FixedPoint Arithmetic library or scale up amount (amount ** 10^18) and scale down(result / (10**18) after performing divison
#0 - brockelmore
2021-12-08T22:49:23Z
duplicates: #148, #198, arguably #145 as well
#1 - brockelmore
2021-12-08T22:51:32Z
We dont want to collect dust anyway. We may end up changing this, but not sure atm.
#2 - 0xean
2022-01-15T01:52:09Z
Marking down to low-risk as the costs of doing so would probably outweigh any benefits on any chain with reasonable gas prices. Increasing the number of decimals used to calc fees could resolve this issue pretty easily.
π Selected for report: gpersoon
Also found by: GiveMeTestEther, Meta0xNull, bitbopper, hack3r-0m, pauliax, pedroais, wuwe1
hack3r-0m
https://github.com/code-423n4/2021-11-streaming/blob/main/Streaming/src/Locke.sol#L197-L201
who
is not used anywhere and can be remvoed since it costs gas for copying it into memory.
#0 - 0xean
2022-01-17T13:48:52Z
dupe of #125