A summary with the following structure: {root cause} will cause [a/an] {impact} for {affected party} as {actor} will {attack path}
When a boost creator create a boost, incentives are also created. There are 2 types of strategies for ERC20Incentive
, the first is POOL
and the second is RAFFLE
. Problems arise when boost creator use RAFFLE
type incentives, this is because the claim()
function in ERC20Incentive
only enter the address of boost participants into the array that will be drawn when drawRaffle()
is called. While drawRaffle()
itself cannot be called because of the onlyOwner
modifier where the boost core contract as the owner does not have a function to call drawRaffle()
on ERC20Incentive
. This causes boost participants to never get their incentives because the raffle winners cannot be drawn.
NOTE
This issue is not about the boost creator or owner behaving maliciously to prevent boost participants from claiming their rewards but about the broken functionality of ERC20Incentive.strategy:RAFFLE
In BoostCore.sol there is no function to call drawRaffle()
function on ERC20Incentive
- Broken functionality when boost uses
ERC20Incentive
of theRAFFLE
type as incentives for boost participants - Boost participants not getting their incentives
- Boost creator create a boost by calling the
createBoost()
function - Then incentives are created with the
_makeIncentives()
function, where the incentive initiation process occurs - Next to incentives initiation process by calling the
initialize()
function inERC20Incentive
contract
function initialize(bytes calldata data_) public override initializer {
InitPayload memory init_ = abi.decode(data_, (InitPayload));
if (init_.reward == 0 || init_.limit == 0) revert BoostError.InvalidInitialization();
// Ensure the maximum reward amount has been allocated
uint256 maxTotalReward = init_.strategy != Strategy.RAFFLE ? init_.reward * init_.limit : init_.reward;
uint256 available = init_.asset.balanceOf(address(this));
if (available < maxTotalReward) {
revert BoostError.InsufficientFunds(init_.asset, available, maxTotalReward);
}
asset = init_.asset;
strategy = init_.strategy;
reward = init_.reward;
limit = init_.limit;
_initializeOwner(msg.sender);
}
there is _initializeOwner(msg.sender)
, which means the BoostCore
is the owner of the ERC20Incentive
contract
- Boost participants call
claimIncentive()
, but it can be seen in theclaim()
function inERC20Incentive
that it only enters the boost participants address into the entries array ifStrategy.RAFFLE
function claim(address claimTarget, bytes calldata) external override onlyOwner returns (bool) {
if (!_isClaimable(claimTarget)) revert NotClaimable();
if (strategy == Strategy.POOL) {
claims++;
claimed[claimTarget] = true;
asset.safeTransfer(claimTarget, reward);
emit Claimed(claimTarget, abi.encodePacked(asset, claimTarget, reward));
return true;
} else {
claims++;
claimed[claimTarget] = true;
entries.push(claimTarget);
emit Entry(claimTarget);
return true;
}
}
- Then to find out the winner of
RAFFLE
and send the prize as an incentive, thedrawRaffle()
function needs to be called. But becauseBoostCore
as the owner ofERC20Incentive
does not have a function to calldrawRaffle()
, thedrawRaffle()
function cannot be called and the winner is never known and the incentive is never sent
function drawRaffle() external override onlyOwner {
Consider add function to call drawRaffle()
on BoostCore
with modifier (for boost owner or admin roles)