Alright, so far I still don’t understand why this transaction didn’t trigger the _entropyCallback() . My implementation doesn’t exceed the gas limit, but I’m attaching it here in case you want to test it.
/// @notice Callback function called by the entropy provider with random number
/// @dev Processes the hit result and awards prizes if applicable
/// @param sequenceNumber Unique identifier for this hit attempt
/// @param provider Address of the entropy provider
/// @param randomNumber Random number provided by the entropy service
function entropyCallback(
uint64 sequenceNumber,
address provider,
bytes32 randomNumber
) internal override {
address user = numberToPlayer[sequenceNumber];
require(user != address(0), "Invalid user address");
hasBeenHit += 1;
uint256[3] memory sequence = generateSequence(
randomNumber,
secondNumberMaxRange
);
uint256 prize = getPrizeForSequence(sequence);
if (prize > 0) {
// Calculate the amount that the owner should be able to withdraw
uint256 ownerWithdrawable = originalPrizePool - ownerWithdrawn;
// Calculate the actual balance after paying the prize (excluding fees)
uint256 balanceAfterPrize = token.balanceOf(address(this)) -
creatorBenefits -
platformBenefits -
prize;
// If the balance after prize payment would be less than what the owner can withdraw,
// adjust the originalPrizePool to reflect that the prize is coming from the owner's investment
if (balanceAfterPrize < ownerWithdrawable) {
// The difference is how much of the owner's investment is being used for the prize
uint256 difference = ownerWithdrawable - balanceAfterPrize;
// Adjust originalPrizePool to reflect this reduction
// Just reduce originalPrizePool by the minimum of difference and originalPrizePool
originalPrizePool = difference >= originalPrizePool
? 0
: originalPrizePool - difference;
}
sendPrize(prize, user);
}
delete numberToPlayer[sequenceNumber];
playerNumbers[user] = 0;
emit HitResult(
user,
provider,
sequenceNumber,
sequence[0],
sequence[1],
sequence[2],
hasBeenHit,
hasBeenPaid,
address(this),
token.balanceOf(address(this)),
prize,
pinataIsBroken,
address(token)
);
}
/// @notice Generates a sequence of three random numbers
/// @dev Creates three random numbers within the specified range using the provided random number
/// @param randomNumber The source random number
/// @param maxRange The maximum value for the generated numbers
/// @return A fixed array of three random numbers
function generateSequence(
bytes32 randomNumber,
uint256 maxRange
) internal pure returns (uint256[3] memory) {
uint256 firstResult = mapRandomNumber(
keccak256(abi.encodePacked(randomNumber, "first")),
1,
maxRange
);
uint256 secondResult = mapRandomNumber(
keccak256(abi.encodePacked(randomNumber, "second")),
1,
maxRange
);
uint256 thirdResult = mapRandomNumber(
keccak256(abi.encodePacked(randomNumber, "third")),
1,
maxRange
);
uint256[3] memory sequence = [firstResult, secondResult, thirdResult];
return sequence;
}
/// @notice Calculates the prize amount for a given sequence of numbers
/// @dev Determines if the sequence wins a prize and how much based on matching patterns
/// @param _sequence Array of three numbers representing the hit result
/// @return The prize amount (0 if no prize)
function getPrizeForSequence(
uint256[3] memory _sequence
) public view returns (uint256) {
// Create sequence array directly without additional conversion
uint8[] memory sequence = new uint8[](3);
for (uint256 i = 0; i < 3; i++) {
require(
_sequence[i] <= type(uint8).max,
"Sequence number too large"
);
sequence[i] = uint8(_sequence[i]);
}
// Check for jackpot (all numbers match maximum)
if (
sequence[0] == secondNumberMaxRange &&
sequence[1] == secondNumberMaxRange &&
sequence[2] == secondNumberMaxRange
) {
return token.balanceOf(address(this)) - creatorBenefits - platformBenefits;
}
// Calculate prize based on sequence pattern
if (sequence[0] == sequence[1] && sequence[1] == sequence[2]) {
// All three numbers match
return sequence[0] * hitCost;
} else if (
sequence[0] == sequence[1] ||
sequence[1] == sequence[2] ||
sequence[0] == sequence[2]
) {
// Two numbers match (adjacent or first and last)
uint8 matchingNumber;
if (sequence[0] == sequence[1]) {
matchingNumber = sequence[0];
} else if (sequence[1] == sequence[2]) {
matchingNumber = sequence[1];
} else {
// sequence[0] == sequence[2]
matchingNumber = sequence[0];
}
return (hitCost * matchingNumber) / 2;
}
return 0;
}
/// @notice Sends a prize to the winner after deducting fees
/// @dev Calculates fees, updates state, and transfers the prize amount
/// @param _amount The gross prize amount before fees
/// @param _winner The address of the prize winner
function sendPrize(uint256 _amount, address _winner) internal {
require(_winner != address(0), "Invalid winner address");
require(_amount > 0, "Prize amount must be greater than 0");
require(
_amount <=
token.balanceOf(address(this)) - creatorBenefits - platformBenefits,
"Insufficient contract balance"
);
// Calculate fees using basis points
uint256 creatorAmount = (_amount * creatorFee) / 10000;
uint256 platformAmount = (_amount * platformFee) / 10000;
uint256 finalPrizeAmount = _amount - creatorAmount - platformAmount;
// Check if this is a jackpot win (all max numbers)
bool isJackpot = _amount ==
(token.balanceOf(address(this)) - creatorBenefits - platformBenefits);
// Update state before transfer
creatorBenefits += creatorAmount;
platformBenefits += platformAmount;
hasBeenPaid += finalPrizeAmount;
if (isJackpot) {
pinataIsBroken = true; // Set pinata as broken when jackpot is hit
emit UserWonJackpot(
_winner,
finalPrizeAmount,
address(this),
token.balanceOf(address(this)),
address(token)
);
} else {
emit UserWonPrize(
_winner,
finalPrizeAmount,
address(this),
token.balanceOf(address(this)),
address(token)
);
}
// Send prize to user
token.safeTransfer(_winner, finalPrizeAmount);
}
My suspicion is this: the error likely isn’t from Pyth but from the AGW (Abstract Global Wallet). When I executed this transaction, I closed the window before receiving confirmation. This shouldn’t cause the failure, but at the same time, this is the only transaction out of hundreds that failed to call _entropyCallback() , and the only one I canceled before getting confirmation.