Platform: Code4rena
Start Date: 02/08/2023
Pot Size: $42,000 USDC
Total HM: 13
Participants: 45
Period: 5 days
Judge: hickuphh3
Total Solo HM: 5
Id: 271
League: ETH
Rank: 28/45
Findings: 1
Award: $89.63
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: Aymen0909
Also found by: 0xStalin, 0xbepresent, Arz, D_Auditor, Jorgect, T1MOH, bin2chen, dirk_y, josephdara, ptsanev, rvierdiiev, seerether, shirochan, trachev
89.6296 USDC - $89.63
An arbitrary user can set a wining number and set a _rewardRecipient to take the rewards
The contract RngRelayAuction is setting a rngAuctionRelayer in the constructor.
file: pt-v5-draw-auction/src/RngRelayAuction.sol /// @notice The relayer that RNG results must originate from. /// @dev Note that this may be a Remote Owner if relayed over an ERC-5164 bridge. address public immutable rngAuctionRelayer;
file: pt-v5-draw-auction/src/RngRelayAuction.sol constructor( PrizePool prizePool_, address _rngAuctionRelayer, uint64 auctionDurationSeconds_, uint64 auctionTargetTime_ ) { ... rngAuctionRelayer = _rngAuctionRelayer; ... }
However this relayer is never used, Now look the rngComplete:
file: pt-v5-draw-auction/src/RngRelayAuction.sol function rngComplete( uint256 _randomNumber, uint256 _rngCompletedAt, address _rewardRecipient, uint32 _sequenceId, AuctionResult calldata _rngAuctionResult ) external returns (bytes32) { if (_sequenceHasCompleted(_sequenceId)) revert SequenceAlreadyCompleted(); uint64 _auctionElapsedSeconds = uint64(block.timestamp < _rngCompletedAt ? 0 : block.timestamp - _rngCompletedAt); if (_auctionElapsedSeconds > (_auctionDurationSeconds-1)) revert AuctionExpired(); ... uint32 drawId = prizePool.closeDraw(_randomNumber); //setting wining number ... for (uint8 i = 0; i < _rewards.length; i++) { uint104 _reward = uint104(_rewards[i]); if (_reward > 0) { prizePool.withdrawReserve(auctionResults[i].recipient, _reward); //sending the rewards emit AuctionRewardDistributed(_sequenceId, auctionResults[i].recipient, i, _reward); } } return bytes32(uint(drawId)); }
We can see that this function is never checking for the caller allowing a user pass a ramdom number and pass a winners.
We can analize the calls that this function make to the prizePool contract:
// this function is in the prizePool contract function closeDraw(uint256 winningRandomNumber_) external onlyDrawManager returns (uint16) {
function withdrawReserve(address _to, uint104 _amount) external onlyDrawManager {
An user can pass those values for his convinience taking the reward for free.
manual
The contract should check for the rngAuctionRelayer which is the only one that can call the rngComplete function
Access Control
#0 - c4-pre-sort
2023-08-08T03:25:37Z
raymondfam marked the issue as duplicate of #82
#1 - c4-judge
2023-08-14T02:48:19Z
HickupHH3 marked the issue as satisfactory