Randomness

WinMoon uses on-chain data to generate winner(s). Read on to see exactly how we calculate player entries, and then to select the winners.

How are entries calculated?

Let's first discuss entry count. Every holder will get an entry automatically. There are various boosts that can be applied to drastically increase a players chances to win. Take a look at the following chart to see:

A few things to note here:

  • SuperBoost multiplies with other boosts. Most entries is achieved by having SuperBoost and then doing a 0.1BNB buy daily.

  • SuperBoost lasts forever. Other boosts last for a single day / single draw.

  • Daily boost is automatically applied with any purchase from our dApp. A simple strategy is to purchase 0.1BNB daily to collect both bonuses with one transaction.

Entries are calculated by creating an array of data, containing each players wallet, along with a running total of total entries, including their own.

0 -> { 1, player1} as player1 has 1 entry
1 -> {16, player2} as player2 has 15 entry
2 -> {21, player3} as player3 has 5 entry
3 -> {22, player4} as player4 has 1 entry
4 -> {37, player5} as player5 has 15 entry

The exact code that does this is:

	function _calculateEntries() internal {
		totalEntries[epoch] = 0;
		for (uint256 i = 0; i < _allOwners.length; i++) {
			address player = _allOwners[i];
			if (automatedMarketMakerPairs[player]) {
				continue;
			}

			uint256 numEntries = (walletBoosts[player] * 100) / 100;
			if (epochBoosts[epoch][player] > 100) {
				numEntries = (numEntries * epochBoosts[epoch][player]) / 100;
			}
			EntriesData memory entryBought = EntriesData({
				player: player,
				currentEntriesLength: uint256(totalEntries[epoch] + numEntries)
			});

			entriesList[epoch].push(entryBought);
			playerEntryIndex[epoch][player] = entriesList[epoch].length - 1;
			totalEntries[epoch] = totalEntries[epoch] + numEntries;
		}
	}

Random Number Generation

We are using on-chain methods to generate randomness. We are taking a hash of a combination of the address of the user who sends the transaction, the last block hash, and the current timestamp + a random number supplied to the function. Here is the code:

	function generateRandomNumber(
		uint randNonce
	) internal view returns (uint256) {
		uint256 blockNumber = block.number - 1; // Use the previous block's hash
		bytes32 lastBlockHash = blockhash(blockNumber);
		return
			uint256(
				keccak256(
					abi.encodePacked(
						_msgSender(),
						lastBlockHash,
						block.timestamp + randNonce
					)
				)
			);
	}

Safety of On-Chain Randomness

The reason why we chose to use on-chain randomness is we feel it cannot be cheated, and I will explain why below.

  • New winners can only be picked exactly 24 hours after the previous epoch/day has begun. This means that with automation, we can "rollover" the epoch at the first possible block after 24 hours. Entry calculation and winner selection is done in the rollover transaction. Because you need to be a holder of WinMoon to benefit from the draw, the ability to potentially see the winner, 1 block in advance, is not a security issue.

  • Gelato.network will be used to attempt the rollover function every single block, to ensure that rollover always happens immediately (first block it is possible). This prevents a bot/player from strategically waiting until a rollover would cause a favorable winner.

Using a random number to pick winners

We simply generate a random number using the above defined method, then adjust the random number to fall in between the range 1 and total entries. Then a winner is selected by picking the entry that falls within the range of the winning number. Let's look at our example from above again:

0 -> { 1, player1} as player1 has 1 entry // (2.7% chance of winning)
1 -> {16, player2} as player2 has 15 entry // (40.5% chance of winning)
2 -> {21, player3} as player3 has 5 entry // (13.5% chance of winning)
3 -> {22, player4} as player4 has 1 entry // (2.7% chance of winning)
4 -> {37, player5} as player5 has 15 entry // (40.5% chance of winning)

In this case, we would convert our large random number into a number from 1 to 37. Let's look at some examples to see who wins in which cases:

Please note, winner entry ranges above are inclusive, including both the start and finish numbers.

Last updated