The distribute() function will reach a gas limit if the winners array is bigger than ~1000 addresses. This is a limitation on the design of the contract, and should be noted. Changes could be made so that the contract has no limit on number of winners.
In the way it was designed, the distribute() function will have a maximum of winners possible to execute. As the codebase stands it seems that if there are ~1000 addresses the loop of transaction PUSH will fail by reaching the block gas limit.
On polygon and ethereum the current gas limit per block, and per transaction, is 30 million.
The following POC can be added to ProxyTest.t.sol
function testGasLimit()
public
setUpContestForNameAndSentAmountToken("James", jpycv2Address, 10000 ether)
{
bytes32 randomId_ = keccak256(abi.encode("James", "001"));
bytes memory data = createDataToDistributeJpycv2();
// prepare data
uint160 numberOfWinners = 950;
address[] memory winners = new address[](numberOfWinners);
uint256[] memory percentages_ = new uint256[](numberOfWinners);
for (uint160 i = 1; i < numberOfWinners+1; i++) {
winners[i-1] = address(i);
percentages_[i-1] = 10;
}
data = abi.encodeWithSelector(Distributor.distribute.selector, jpycv2Address, winners, percentages_, "");
vm.startPrank(organizer);
deployedProxy = proxyFactory.deployProxyAndDistribute(randomId_, address(distributor), data);
vm.stopPrank();
}
Run the gas report with forge test --match-test testGasLimit --gas-report
The gas report will show that the transaction with 950 winners will cost 27 million gas, which is close to the block gas limit.
This is a limitation on the current design of the contract, and the creators have one of two options, redesign the protocol for unlimited number of winners, or document the limitation.
Forge gas-report
Divide the distribution of the tokens in multiple steps.
Organizer adds the winners to a mapping on storage with their percentages. This could be done in multiple transactions, until the full list is achieved. Add the percentages of each winner to a total percentage.
A struct could be used to store the winner and percentage in one slot.
struct Winner {
address winner; // 20 bytes
uint96 percentage; // 12 bytes
}
Organizer calls a function to validate the total percentage is 100%. At this point the state of the contract changes and the winners would be able to withdraw their funds with a Pull pattern.
Users withdraw their funds with a pull pattern.
Or document the limitation.
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.