Skip to main content

Verify a YOLO result

  1. Go to the YOLO history and click Verify.
  2. Copy the transaction hash.
  3. Paste the transaction hash in the input field below.
  4. Click Verify to double-check the YOLO result.

How does the verification process work?

The verification process involves the following steps:

  1. Fetch the transaction receipt and block information.
  2. Decode the logs to extract the drand round used by Gelato VRF and the requestId.
  3. Fetch the drand chain information to determine the randomness delay.
  4. Fetch the drand randomness and calculate the random value.
  5. Validate the drand beacon randomness.
  6. Fetch the YOLO round and determine the expected winner based on the random value.
  7. Compare the expected winner with the actual winner to verify the YOLO result.

What is drand?

Drand is a distributed randomness beacon service that provides publicly verifiable randomness.

The drand service is used by Gelato VRF to generate randomness for YOLO. You can find out more about drand by checking out the drand and Gelato VRF documentations.

How can I verify the YOLO result via code?

The following JavaScript code snippet shows how the verification process works.

Note: The following implementation requires v6 of the ethers library.

import { Interface, JsonRpcProvider, AbiCoder, keccak256, Contract } from "ethers";
import { verifyBeacon } from "drand-client/beacon-verification";

// Configuration constants
const CONFIG = {
BLAST_CHAIN_ID: 81457,
GELATO_VRF_CONSUMER: "0x95c68c52bb12a43069973FDCD88e4e93d2142f10",
DRAND_API_URL: "https://drand.cloudflare.com/",
DRAND_CHAIN_HASH: "52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971",
YOLO_CONTRACT_ADDRESS: "0x0000000000E14E87e5c80A8A90817308fFF715d3",
BLAST_PROVIDER_URL: "https://rpc.blast.io",
};

// Custom error class for invalid input
class InvalidInput extends Error {
constructor() {
super("Transaction not supported. Please ensure the transaction hash references a valid randomness request transaction for YOLO.");
this.name = "InvalidInput";
}
}

// Setup the contract interface, provider and contract instance
const yoloInterface = new Interface([
"event RequestedRandomness(uint256 drandRoundId, bytes data)",
"event RandomnessRequested(uint256 roundId, uint256 requestId)",
"function getRound(uint256) view returns (uint8, uint40, uint16, uint40, uint40, uint40, address winner, uint96, uint256, tuple(uint8, address, uint256, uint256, address depositorAddress, bool, uint40 entryIndex)[] deposits)"
]);
const blastProvider = new JsonRpcProvider(CONFIG.BLAST_PROVIDER_URL);
const yoloContract = new Contract(CONFIG.YOLO_CONTRACT_ADDRESS, yoloInterface, blastProvider);

/**
* Encodes and hashes the provided data.
* @param {string[]} dataTypes Types of data to encode
* @param {string[]} values Values to encode
* @returns {string} The keccak256 hash of the encoded data
*/
function encodeAndHash(dataTypes, values) {
const abiCoder = new AbiCoder();
const encoded = abiCoder.encode(dataTypes, values);
return keccak256(encoded);
}

/**
* Processes the drand randomness and request id to generate the random value used.
* @param {string} drandRandomness The randomness from drand
* @param {number} requestId The request id for randomness
* @returns {bigint} The calculated random value
*/
function retrieveRandomValue(drandRandomness, requestId) {
const firstHash = encodeAndHash(
["uint256", "address", "uint256", "uint256"],
[`0x${drandRandomness}`, CONFIG.GELATO_VRF_CONSUMER, CONFIG.BLAST_CHAIN_ID, requestId]
);
const secondHash = encodeAndHash(["uint256", "uint256"], [firstHash, 0]);
return BigInt(secondHash);
}

/**
* Verifies the YOLO result for a given transaction hash.
* @param {string} transactionHash The transaction hash to verify
* @returns {Promise<{isValidBeacon: boolean, differenceSeconds: number, roundId: number, randomValue: bigint, expectedWinner: string, actualWinner: string}>} The result of the verification process
*/
export async function verifyYoloResult(transactionHash) {
const receipt = await blastProvider.getTransactionReceipt(transactionHash);

if(!receipt) {
throw new InvalidInput();
}

const block = await blastProvider.getBlock(receipt.blockNumber);

const randomnessRequestLog = receipt.logs.find(log => log.topics[0] === "0xd91fc3685b930310b008ec37d2334870cab88a023ed8cc628a2e2ccd4e55d202");
const randomnessRequestedLog = receipt.logs.find(log => log.topics[0] === "0x3d94fecedaa4f90b8bd459797adb95f5bb11426025c5541390d9ccc1ad1b60a1");

if (!randomnessRequestLog || !randomnessRequestedLog) {
throw new InvalidInput();
}

const { drandRoundId } = yoloInterface.decodeEventLog("RequestedRandomness", randomnessRequestLog.data, randomnessRequestLog.topics);
const { roundId, requestId } = yoloInterface.decodeEventLog("RandomnessRequested", randomnessRequestedLog.data, randomnessRequestedLog.topics);

// Fetch drand chain info
const drandChainInfoRes = await fetch(`${CONFIG.DRAND_API_URL}/${CONFIG.DRAND_CHAIN_HASH}/info`);
const chainInfo = await drandChainInfoRes.json();
const drandRoundTimestamp = Number(drandRoundId) * chainInfo.period + chainInfo.genesis_time;
const differenceSeconds = drandRoundTimestamp - block.timestamp;

// Fetch drand randomness
const drandRandomnessRes = await fetch(`${CONFIG.DRAND_API_URL}/${CONFIG.DRAND_CHAIN_HASH}/public/${drandRoundId}`)
const beacon = await drandRandomnessRes.json();
const randomValue = retrieveRandomValue(beacon.randomness, requestId);

// Verify the beacon randomness
const isValidBeacon = await verifyBeacon(chainInfo, beacon, Number(drandRoundId));

// Fetch YOLO round info
const { winner, deposits } = await yoloContract.getRound(roundId);
const winningEntry = randomValue % deposits[deposits.length - 1].entryIndex + 1n;

// Determine the expected winner
for (const deposit of deposits) {
if (deposit.entryIndex >= winningEntry) {
console.log(`Is the randomness valid: ${isValidBeacon} \nThe random value was generated ${differenceSeconds} seconds after the round ${roundId} ended. \nThe expected winner is ${deposit.depositorAddress}. \nThe actual winner is ${winner}.`);
return { isValidBeacon, differenceSeconds, roundId, randomValue, expectedWinner: deposit.depositorAddress, actualWinner: winner };
}
}
}

// Example usage
verifyYoloResult("TRANSACTION_HASH").catch(console.error);