Platform: Code4rena
Start Date: 04/01/2022
Pot Size: $25,000 USDC
Total HM: 3
Participants: 40
Period: 3 days
Judge: Ivo Georgiev
Total Solo HM: 1
Id: 75
League: ETH
Rank: 11/40
Findings: 2
Award: $523.77
π Selected for report: 0
π Solo Findings: 0
π Selected for report: leastwood
Also found by: MaCree, WatchPug, cmichel, egjlmn1, kenzo, onewayfunction, sirhashalot
140.1442 USDC - $140.14
egjlmn1
An attacker can prevent any user from locking assets due to the unsafe id generation for the nfts. https://github.com/XDeFi-tech/xdefi-distribution/blob/master/contracts/XDEFIDistribution.sol#L242
The id is generated by using only the amount deposited, the duration to lock and the totalSiupply of nfts. The first two are easy for the attacker to copy, so all he needs to do is make sure the totalSupply for his nft is the same totalSupply when the user will want to lock his assets. The attacker can do it by merging two tokens.
The attack will go as follow:
initialTotalSupply+2
totalSupply
of nfts by 2 after the _burn
in the loop (in line 217), which reduce the totalSupply
to initialTotalSupply+1
and increase it by 1 after the _safeMint
right after the loop (in line 223) which increase the totalSupply
to the initialTotalSupply+2
.ERC721
will revert.A complete DOS attack for any user and any amount (given the attacker as enough money, doesn't need much more than the user) Once the attacker locks all his money he can just wait to unlock it DOS everybody again
the _safeMint
function increase the totalSupply by 1 and the _burn
reduce it by 1.
the attacker locks his third asset when totalSupply is +2 so after that the totalSupply is +3, but the merging function will call _burn
twice and safeMint
once so the totalSupply will be +3-2+1 = +2 and thus the user will fail his lock.
the merge wont fail because the amount to safeMint in the merge is the sum of the two burt tokens
Manual code review
add another factor for the id generation, like msg.sender (this will prevent dos attacks but users that do the same steps as the attacker supposed to do: lock, lock, lock, merge(1,2) and than try to lock the same amount and duration right after will fail so maybe warn them in the UI)
#0 - deluca-mike
2022-01-09T05:05:27Z
Valid and duplicate of #17, but not high-risk since no funds lost, and a lock for a very specific amount and duration would be temporarily denied under specific transient conditions.
π Selected for report: TomFrenchBlockchain
142.4013 USDC - $142.40
egjlmn1
in the function _toUint256Safe(int256 x_)
https://github.com/XDeFi-tech/xdefi-distribution/blob/master/contracts/XDEFIDistribution.sol#L288
you use both a named return value and a return statement.
this is unclear why and should be changed.
The similar function _toInt256Safe(uint256 x_)
use only named return value, and those two functions should probably act the same.
This causes unclear code and may be confusing since the two functions should act similar
Two possible cases:
if code readability is more important, remove the return statement in _toUint256Safe(int256 x_)
and change it to y = uint256(x_);
if gas optimization is more of an issue remove the named return value y
in the function declaration since it waste over 20 gas each function call, and since this is just a casting function it should be as efficient as it can.
#0 - deluca-mike
2022-01-05T08:41:51Z
Yup, I like this. Will go with the option to assign return variables in order to keep the function prototype pattern/style consistent. However, this should be informational (i.e. no risk).
#1 - deluca-mike
2022-01-09T10:26:34Z
Duplicate #2
210.9649 USDC - $210.96
egjlmn1
https://github.com/XDeFi-tech/xdefi-distribution/blob/master/contracts/XDEFIDistribution.sol#L285 https://github.com/XDeFi-tech/xdefi-distribution/blob/master/contracts/XDEFIDistribution.sol#L289
the following lines use assert to validate user input, it's wrong and should be replaced with require. here is an article that explains why you should always use require to validate user input: https://medium.com/blockchannel/the-use-of-revert-assert-and-require-in-solidity-and-the-new-revert-opcode-in-the-evm-1a3a7990e06e
users who accidentally provide invalid input will not be refunded by the remaining gas, and it can be a very high amount.
replace assert with require in the lines I wrote.
#0 - deluca-mike
2022-01-05T08:45:16Z
Good catch. Will be converting these to if-revert
s with new custom error messages. However, disagree with severity since its highly unlikely these would be triggered, and if they did, is not so much as a risk as it is a waste.
#1 - deluca-mike
2022-01-09T10:25:20Z
Duplicate #14
30.2712 USDC - $30.27
egjlmn1
if a user want to get his rewards when he unlock his assets he must first update the distribution or else the rewards won't be counted.
lets say the distribution was just funded rewards, and a user tries to unlock his assets without first updating the distribution, those rewards won't be counted because the _pointsPerUnit
won't increase.
and he wont be able to get those rewards afterwards if he update because unlock()
call _updateXDEFIBalance
at the end and those rewards won't be counted
the only place where points are updated is in updateDistribution()
and it uses the calculation of _updateXDEFIBalance()
which is also called in unlock()
manual code review
in the ui make sure that every time a user calls unlock()
, it first calls updateDistribution()
#0 - deluca-mike
2022-01-09T06:24:15Z
Technically valid, but this was expected, and we disagree with severity, as explained in the duplicate issue #30.