Due to a hardcoded value that is used to calculate the expected totalPercentage
of the funds that should be distributed to selected winners (aka "supporters"), organisers (aka "innovators") and the owner of the protocol are unable to distribute or recover funds, in case the BASIS_POINTS
and COMMISSION_FEE
of the Distributor
contract are adjusted as part of an upgrade.
Organizers distribute contest funds, that were previously sent by "sponsors" of a contest to a pre-calculated proxy contract address, to selected winners via ProxyFactory.deployProxyAndDistribute(bytes32 contestId, address implementation, bytes calldata data)
.
The selection of the winners and the distribution of price funds is calculated off-chain and sent as data
to the aforementioned function, which is then ultimately passed to Distributor._distribute(address token, address[] winners, uint256[] percentages)
.
For distribution calculations, the protocol uses BASIS_POINTS
and COMMISSION_FEE
defined in Distributor
contract, which are 10000
and 500
respectively at the time of the audit. Meaning 10000 = 100%
total funds and 500 = 5%
commission fees that go to the stadium address.
The protocol ensures that the provided percentages
add up to 100% (BASIS_POINTS
), minus the COMMISSION_FEE
. If the percentages don't add up as expected, the input data is considered incorrect and the transaction reverts.
Here's the relevant snippet where the check is performed.
If the check passes, the contest's funds are sent to the selected winners proportionally using BASIS_POINTS
to calculate the distributions.
As an example, assuming we have two winners with each getting 47,5% of the funds (5% COMMISSION_FEE
go to the protocol) and the contest contract has a total of 100000
units of a given token, then each winner would get 47500
units:
In the aforementioned check where the protocol ensures that the provided percentages
properly add up, considering the commission fee, the total BASIS_POINTS
value is hardcoded instead of using BASIS_POINTS
directly.
This can result in scenarios where the Distributor
contract's BASIS_POINTS
and COMMISSION_FEE
values are adjusted/upgraded, leaving the check untouched, and making it impossible for both, organisers and owners, to either distribute or recover the funds.
Here's how:
Assume the protocol decides to change the granularity of BASIS_POINTS
from 10000
to 100000
and COMMISSION_FEE
from 500
to 5000
respectively, but forgets to update the check with the new BASIS_POINTS
value. In such a scenario, all calls to Distributor._distribute()
will revert, even if the provided data
has adjusted percentages
, because the check still uses 10000
as a base for 100%.
Make the following changes to Distributor.sol
:
Add a new file AuditTest.t.sol
in test/integration/
with the following test code:
Then, ensure that createDataToDistributeJpycv2()
has adjusted percentages
data as well:
Execute the test with:
The test will pass as both calls deployProxyAndDistribute()
and deployProxyAndDistributeByOwner()
are going to fail due to the bug in the aforementioned check.
I've marked this finding as medium risk because, even though this could make pretty much all contest funds unrecoverable, the contracts have great test coverage and such an adjustment will most likely be caught by tests.
Manual review for finding the bug.
Foundry for building the proof of concept.
Instead of hardcoding the current BASIS_POINTS
value in the mentioned check, use the BASIS_POINTS
variable instead:
Making this adjustment, upgrades/adjustments done to BASIS_POINTS
and COMMISSION_FEE
no longer make contest funds unrecoverable as the check that previously reverted will no rely on the value stored in BASIS_POINTS
.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.