HackIndia Logo
HackathonsNewsSkillsGrowth
HackIndia LogoContact: +91 9910125804

General

  • Companies
  • Colleges
  • Board of Advisors
  • Brand Ambassadors
  • FAQ
  • Contact
  • Report a Bug

Hackathons

  • Upcoming Hackathons
  • Judges
  • Mentors
  • Partners
  • Participating Colleges
  • Testimonials

Resources

  • About Us
  • Branding
  • Latest News
  • Growth
  • Skills
  • Builders
  • Ecosystem
  • Forum
  • Ideas
  • Prompt Optimizer
  • Games

© 2026 HackIndia. All rights reserved.

Privacy PolicyTerms of ServiceCode of ConductSitemap

Build 2.139 · 05 Nov 2026, 10:27 UTC

    HackIndia
    HackathonsNewsInternshipsSkillsGrowthPro Perks
    ← Ship a safer Solidity contract

    All the reading

    Solidity

    8 lessons, about 34 minutes. The check's questions come from these.

    Contents
    Writing clear contract state
    1. 1. Write your first clean contract
    2. 2. Use types and data locations
    Controlling changes and tests
    1. 3. Control state and access
    2. 4. Set up Hardhat tests
    3. 5. Spend less gas by design
    Safer releases and repo setup
    1. 6. Avoid student-contract vulnerabilities
    2. 7. Deploy to a testnet
    3. 8. Submit the recruiter-ready repo

    Writing clear contract state

    1. Write your first clean contract

    5 min

    In 30 seconds
    • Create a small Solidity 0.8.x contract with a clear purpose.
    • Name state, events, errors, and functions so a reviewer understands it fast.
    • Avoid old tutorial patterns that make beginner contracts look unsafe.
    Key point

    One clean idea

    A smart contract is code plus data at an Ethereum address. Your first goal is not to add many features. Your goal is to make the state and the allowed changes obvious.

    A recruiter or hackathon judge should understand your contract in one minute, before reading your tests.

    What to include

    A Solidity contract can contain state variables, functions, modifiers, events, errors, structs, enums, and inheritance. In this first lesson, keep it smaller: state, constructor, function, event, and custom error.

    Use Solidity 0.8.x. Do not add pragma experimental ABIEncoderV2. It is an old pattern because ABI coder v2 is already the default in Solidity 0.8.x.

    Example

    First contract

    This example stores one project profile. It is like a mini on-chain record for a HackIndia submission. It has one owner, one public title, and a way to update the title.

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.37;
    
    contract ProjectProfile {
        address public immutable owner;
        string public title;
    
        error NotOwner(address caller);
        error EmptyTitle();
    
        event TitleUpdated(string oldTitle, string newTitle);
    
        constructor(string memory initialTitle) {
            if (bytes(initialTitle).length == 0) {
                revert EmptyTitle();
            }
    
            owner = msg.sender;
            title = initialTitle;
        }
    
        function updateTitle(string calldata newTitle) external {
            if (msg.sender != owner) {
                revert NotOwner(msg.sender);
            }
    
            if (bytes(newTitle).length == 0) {
                revert EmptyTitle();
            }
    
            string memory oldTitle = title;
            title = newTitle;
    
            emit TitleUpdated(oldTitle, newTitle);
        }
    }
    Your to-do

    2. Use types and data locations

    4 min

    In 30 seconds
    • Choose Solidity types that make your data clear and hard to misuse.
    • Use storage, memory, and calldata in the right place.
    • Update your contract with structs, arrays, mappings, and constants.
    Key point

    One rule

    Types say what a value is. Data location says where complex data lives while the function runs.

    In Solidity 0.8.x, be explicit. A recruiter should understand your contract state by reading the variable declarations.

    Types you will use

    Solidity is statically typed. Every state variable and local variable needs a type. There is no null or undefined, so new variables start with a default value.

    Common defaults are 0 for uint256, false for bool, the zero address for address, and an empty value for string or bytes.

    Example

    Readable contract state

    This example stores student project submissions. It uses simple types, a struct for grouped data, a mapping for lookup, and an array for public listing.

    Use this pattern when your hackathon contract needs records, owners, votes, tasks, certificates, or payments.

    Controlling changes and tests

    3. Control state and access

    5 min

    In 30 seconds
    • Protect state changes with clear ownership and modifiers.
    • Use msg.sender checks, not tx.origin, for access control.
    • Emit events so your repo shows what changed and who did it.
    Key point

    One rule

    State is the data your contract keeps between calls. Access control is the rule for who can change that data.

    Any function that changes important state must answer two questions: who is allowed, and what proof will show the change happened.

    State needs guards

    A contract is code plus data at an address. Storage stays after the transaction ends, so a bad state change stays too unless a later transaction fixes it.

    Use public reads when useful, but keep writes narrow. Fees, roles, balances, owners, and status flags should not be changeable by any wallet.

    Example

    Owner and events

    This pattern is small enough to understand. It sets an owner once, restricts admin actions, and emits an event when state changes.

    Safer releases and repo setup

    6. Avoid student-contract vulnerabilities

    4 min

    In 30 seconds
    • Spot the common bugs that make student Solidity projects look unsafe.
    • Use Checks-Effects-Interactions, msg.sender, and bounded work by default.
    • Add a short security notes section that recruiters can read quickly.
    Key point

    One rule

    Assume every public or external function will be called by a stranger, a bot, or another contract. Write the function so it is still safe.

    A safer contract checks first, updates its own state second, and talks to other contracts last. This is Checks-Effects-Interactions.

    Public means public

    Do not store secrets in a contract. Private variables are not secret. Local variables are not secret either. Anyone can inspect contract data on-chain.

    Use Solidity 0.8.x arithmetic checks. Do not add unchecked blocks unless you can explain why wrapping is safe.

    Common mistake

    10 questions, 7 to pass.

    Sign in to take the test

    Read the structure

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done

    Tip

    Use AI carefully

    You can ask ChatGPT, Gemini, or Claude to review the contract for readability. Use a narrow prompt: “Check this Solidity 0.8.x contract for old patterns, unclear names, and missing events. Do not rewrite the whole design.”

    Check the answer yourself. AI may suggest old habits like SafeMath for normal arithmetic, tx.origin, or send. For Solidity 0.8.x beginner code, those are warning signs.

    Common mistake

    Avoid tutorial leftovers

    Do not use tx.origin for access control. Use msg.sender-style checks like this contract does.

    Do not ignore compiler warnings. Modern Solidity warnings often point to real safety or compatibility problems. A clean repo should compile without warnings.

    Why this looks credible

    The contract is small and easy to review. The owner is immutable, so it cannot be changed after deployment. The update function checks permission, checks input, updates state, and emits an event.

    It also avoids Ether handling. That is intentional. Stored Ether, external calls, and withdrawal logic add risk. You will handle safer patterns later in the track.

    Your to-do

    Do this now

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.0;
    
    contract ProjectRegistry {
        uint256 public constant MAX_TITLE_LENGTH = 80;
        address public immutable admin;
    
        struct Project {
            address builder;
            string title;
            string repoUrl;
            bool submitted;
        }
    
        mapping(address => Project) public projectsByBuilder;
        address[] public builders;
    
        constructor() {
            admin = msg.sender;
        }
    
        function submitProject(string calldata title, string calldata repoUrl) external {
            require(bytes(title).length > 0, "Title required");
            require(bytes(title).length <= MAX_TITLE_LENGTH, "Title too long");
            require(!projectsByBuilder[msg.sender].submitted, "Already submitted");
    
            projectsByBuilder[msg.sender] = Project({
                builder: msg.sender,
                title: title,
                repoUrl: repoUrl,
                submitted: true
            });
    
            builders.push(msg.sender);
        }
    }
    Your to-do

    Pick the right type

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done

    Storage, memory, calldata

    Storage is permanent contract state. It stays between transactions and costs more to read and modify, so store only what the contract must remember.

    Memory is temporary and cleared after the call. Calldata is also temporary, but it is the read-only input area for external function parameters.

    Tip

    Use calldata first

    For external functions that receive strings, bytes, arrays, or structs and do not need to edit them, use calldata.

    If you need to change a copy inside the function, use memory. If you need to update contract state, write to storage.

    Common mistake

    Avoid old tutorial habits

    Do not add pragma experimental ABIEncoderV2 in a Solidity 0.8.x project. ABI coder v2 is already the default, and current Solidity warns that this pragma is deprecated.

    Do not store private secrets in variables. Everything in a smart contract is publicly visible, including private state variables and local variables.

    Your to-do

    Do this now

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done

    Use custom errors instead of long revert strings when the error is part of your contract design.

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.37;
    
    contract CampusClub {
        address public immutable owner;
        string public notice;
        bool public paused;
    
        error NotOwner(address caller);
        error Paused();
        error EmptyNotice();
    
        event NoticeChanged(address indexed changedBy, string newNotice);
        event PausedChanged(address indexed changedBy, bool paused);
    
        modifier onlyOwner() {
            if (msg.sender != owner) revert NotOwner(msg.sender);
            _;
        }
    
        modifier whenNotPaused() {
            if (paused) revert Paused();
            _;
        }
    
        constructor(string memory firstNotice) {
            owner = msg.sender;
            notice = firstNotice;
        }
    
        function setNotice(string calldata newNotice) external onlyOwner whenNotPaused {
            if (bytes(newNotice).length == 0) revert EmptyNotice();
            notice = newNotice;
            emit NoticeChanged(msg.sender, newNotice);
        }
    
        function setPaused(bool value) external onlyOwner {
            paused = value;
            emit PausedChanged(msg.sender, value);
        }
    }
    Your to-do

    Read the pattern

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done

    Common mistake

    Do not use tx.origin

    tx.origin is the wallet that started the whole transaction. It can stay the same even when another contract calls your contract in the middle.

    For authorization, use msg.sender. It tells you who called this function directly. Solidity security guidance says never use tx.origin for authorization.

    Tip

    Use OpenZeppelin when bigger

    For a hackathon contract with simple ownership, writing the pattern yourself teaches you what is happening. For a larger repo, use audited building blocks instead of copy-pasting library code.

    OpenZeppelin Contracts 5.x includes Ownable for onlyOwner, Ownable2Step for safer ownership transfer, and Pausable for emergency stops. Install the package instead of copying source files.

    npm install @openzeppelin/contracts
    Common mistake

    Private is not secret

    private only stops other Solidity contracts from reading the variable directly. It does not hide the value from the chain.

    Do not store secrets like OTPs, private keys, exam answers, or hidden auction bids in contract state. Everything used in a smart contract is publicly visible.

    Your to-do

    Do this now

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done

    4. Set up Hardhat tests

    4 min

    In 30 seconds
    • Create a Hardhat 3 project structure for Solidity 0.8.x testing.
    • Put tests in the right place and test behavior, not just deployment.
    • Record local test results in your repo so reviewers can trust it.
    Key point

    One job

    A test proves your contract keeps its promise after each change. For this track, your repo must show that the contract builds and the important actions are checked locally before testnet deployment.

    Why Hardhat here

    Hardhat 3 is a current mainstream Solidity toolkit. It supports Solidity tests, TypeScript tests, and fuzzing.

    A default Hardhat 3 project gives you the folders recruiters expect to see: contracts, test, scripts, ignition/modules, and hardhat.config.ts.

    Your to-do

    Start the project

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done

    npx hardhat --init
    
    # or, if you use pnpm
    pnpm dlx hardhat --init
    
    # or, if you use yarn
    yarn dlx hardhat --init

    What to test

    Do not test only that the contract deploys. Test the rules a user can break.

    For a college project, cover the happy path, at least one rejected action, and one edge case. If your contract has an owner-only function, test that a non-owner cannot call it.

    Example

    Test plan example

    Write the plan before asking AI to generate test code. This keeps the AI from testing random things that look correct but miss your actual rules.

    Contract: EventPass
    
    Rules to test:
    1. The deployer becomes the organizer.
    2. A student can register with a name and college.
    3. The same wallet cannot register twice.
    4. Only the organizer can close registrations.
    5. Registration fails after registrations are closed.
    6. The contract emits an event when a student registers.
    Tip

    Use AI carefully

    You can ask ChatGPT, Gemini, or Claude to draft Hardhat tests from your contract. Paste the contract and your test plan, then ask for tests that match your project type.

    Check the output. It should import from your actual files, use the function names in your contract, and assert failures for restricted actions. Delete tests for features your contract does not have.

    Prompt:
    I have this Solidity 0.8.x contract. Write Hardhat 3 tests for the rules below.
    Use the existing folder structure: contracts/ and test/.
    Do not invent functions.
    Include tests for success cases and expected reverts.
    
    Rules:
    [paste your test plan]
    
    Contract:
    [paste your contract]
    Common mistake

    Common weak tests

    Beginners often write tests that only call functions without checking the result. That does not prove much.

    Every test should end by checking state, an event, a returned value, or a revert. Also take compiler warnings seriously. A repo with warnings looks unfinished.

    Your to-do

    Do this now

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done

    5. Spend less gas by design

    4 min

    In 30 seconds
    • Choose cheaper data locations before you write code.
    • Reduce storage writes, unbounded loops, and repeated reads.
    • Add gas notes that make your contract repo look practical.
    Key point

    One gas rule

    Storage is the expensive part. Keep less data on-chain, write it fewer times, and avoid functions that may loop over growing storage.

    What gas pays for

    A contract is code plus data at an address. The data is state. It stays between transactions, so the EVM charges more when you initialize or change it.

    Memory is temporary for one call. Calldata is the input sent to an external function. Use these when you do not need to save something permanently.

    Your to-do

    Design cheaper state

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done

    Example

    Cheaper registry

    This pattern stores only what the contract needs for checks. The name is emitted for the app and block explorer, but it is not stored forever.

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.37;
    
    contract ProjectRegistry {
        address public immutable organizer;
        mapping(address => bool) public submitted;
    
        event ProjectSubmitted(address indexed builder, string repoUrl);
    
        constructor(address _organizer) {
            organizer = _organizer;
        }
    
        function submitProject(string calldata repoUrl) external {
            require(!submitted[msg.sender], "Already submitted");
            submitted[msg.sender] = true;
            emit ProjectSubmitted(msg.sender, repoUrl);
        }
    }
    Tip

    Use constants well

    If a value never changes, do not store it like normal state. Solidity has constant and immutable variables, and both have much lower gas costs than regular state variables.

    Use constant for compile-time values. Use immutable when the value is known during deployment, like an organizer address passed to the constructor.

    Common mistake

    Loops can brick features

    A loop over a growing storage array can become too expensive. If it grows beyond what fits in a block, that function can stop being usable.

    Do not write payoutAllStudents or verifyEverySubmission if the list can grow during a hackathon. Let users claim one by one, or process small batches with a clear limit.

    Your to-do

    Review with AI

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 4 done

    Your to-do

    Do this now

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done

    Bad authorisation

    Never use tx.origin to decide who is allowed to do something. A phishing contract can trick the original wallet into calling through it.

    Use msg.sender-style checks, a clear owner variable, or a reviewed library such as OpenZeppelin Ownable installed from the package. Do not copy-paste library source into your repo.

    Example

    Safer withdrawal

    This shape is better than pushing Ether to users inside a loop. Each user withdraws their own balance.

    The important part is order: check amount, set balance to zero, then make the external call.

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.0;
    
    contract PrizeVault {
        mapping(address => uint256) public winnings;
    
        error NothingToWithdraw();
        error TransferFailed();
    
        function claim() external {
            uint256 amount = winnings[msg.sender];
            if (amount == 0) revert NothingToWithdraw();
    
            winnings[msg.sender] = 0;
    
            (bool ok, ) = msg.sender.call{value: amount}("");
            if (!ok) revert TransferFailed();
        }
    }
    Tip

    Watch external calls

    Any Ether transfer or call to another contract hands control away. That other contract may call you back before your first function finishes.

    For simple projects, avoid complex cross-contract calls. If you must use them, keep the contract small, test the failure path, and consider ReentrancyGuard from OpenZeppelin Contracts 5.x.

    Common mistake

    Unbounded loops

    A loop over a storage array can work in your test with 3 users and fail later with 3,000 users. If the loop needs more gas than a block allows, the function can become unusable.

    Do not write one admin function that pays every participant, deletes every record, or recalculates every score. Prefer per-user actions, pagination, or fixed-size limits.

    Your to-do

    Security notes file

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done

    Your to-do

    Do this now

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done

    7. Deploy to a testnet

    4 min

    In 30 seconds
    • Prepare a safe testnet deploy without exposing your private key.
    • Record the contract address, network, compiler version, and deployer.
    • Add clear deployment instructions that a reviewer can follow.
    Key point

    One idea

    A testnet deploy is proof that your contract can leave your laptop. Treat it like a small production release: clean config, no leaked secrets, clear notes.

    What counts

    For this track, you need either a testnet deployment address or a deployment script. A stronger repo has both.

    Your README should let a reviewer see what you deployed, where you deployed it, which account deployed it, and how they can repeat the process with their own key.

    Your to-do

    Before deploying

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done

    Common mistake

    Do not leak keys

    Never paste a private key into hardhat.config.ts, a script, or your README. A public GitHub repo is not a private notebook.

    Use environment variables and commit only an example file. If you accidentally commit a key, assume it is burned. Remove it from the wallet and create a new one.

    Example

    Env example

    Commit a sample file that shows the required names. Do not commit the real .env file.

    This is enough for a teammate or recruiter to understand your deploy setup without seeing secrets.

    # .env.example
    TESTNET_RPC_URL="https://your-testnet-rpc-url"
    TESTNET_PRIVATE_KEY="0xreplace_with_testnet_wallet_private_key"
    Tip

    Use AI carefully

    You can ask ChatGPT, Gemini, or Claude to draft your deployment script for your current Hardhat 3 project structure. Paste your contract name, constructor arguments, and folder tree.

    Check the answer before using it. It must not hardcode private keys, must match your contract constructor, and must deploy the correct contract file. If it suggests old Solidity experimental pragmas or send/transfer patterns, reject that part.

    Example

    Deployment notes

    Add a short section like this to your README after you deploy. Keep it boring and exact.

    If you cannot get faucet tokens today, add the deployment script and write “testnet deploy pending” with the blocker. Replace it before final HackIndia submission.

    Deployment
    Network: <testnet name>
    Contract: <contract name>
    Solidity: 0.8.x
    Deployer: 0x...
    Contract address: 0x...
    Date: YYYY-MM-DD
    Local test result: <paste command used and pass summary>
    
    How to deploy
    1. Copy .env.example to .env.
    2. Add your own TESTNET_RPC_URL and TESTNET_PRIVATE_KEY.
    3. Run the project deploy command from this repo.
    4. Paste the new contract address in this section.
    Your to-do

    Do this now

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done

    8. Submit the recruiter-ready repo

    4 min

    In 30 seconds
    • Package your contract repo so a reviewer can run it without asking you anything.
    • Add proof: tests, deployment steps, address or script, and security notes.
    • Submit a public HackIndia repo that looks clean to recruiters.
    Key point

    One reviewer rule

    Your repo should answer three questions fast: what does it do, how do I run it, and what risks did you think about.

    A recruiter or mentor may open it between meetings. Make the first screen useful.

    What to include

    A credible Solidity repo is not only a contract file. It shows structure, proof, and judgement.

    For this track, your public repo needs a Solidity 0.8.x contract, tests, deployment instructions, and a short security notes section. It should also show local test results and a testnet deployment address or a deployment script.

    Your to-do

    Repo checklist

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done

    Example

    README skeleton

    Use this as a minimum. Replace every placeholder before submission.

    Do not paste private keys, seed phrases, RPC secrets, or paid API keys.

    # Project name
    
    Short description: what the contract does and who can call the main functions.
    
    ## Contract
    - Solidity: 0.8.x
    - Main contract: contracts/YourContract.sol
    - Access control: owner-only functions listed here
    
    ## Run locally
    1. Install dependencies
    2. Run tests
    3. Check the test output below
    
    ## Test result
    Paste a short terminal output or screenshot link here.
    Example: all tests passing locally on your machine.
    
    ## Deploy
    Deployment script/module: path/to/deploy/file
    Testnet address: 0x...
    Network: testnet name
    
    ## Security notes
    - Authorization uses msg.sender-based checks, not tx.origin.
    - External calls are placed after state updates where relevant.
    - No unbounded loop is required for normal user actions.
    - Compiler warnings were checked before submission.
    
    ## Known limits
    Write what your contract does not handle yet.
    Common mistake

    Common submission mistakes

    Do not submit only a contract file. That looks like a class assignment, not a shipped project.

    Do not hide the weak parts. A short known limits section is better than pretending the contract is production-ready.

    Tip

    Use AI carefully

    You can ask ChatGPT, Gemini, or Claude to review your README and security notes. Give it your contract summary, not secrets.

    Check the output yourself. AI often makes confident claims like audit-ready, fully secure, or gas-optimized without proof. Remove those claims.

    Your to-do

    Final review flow

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done

    Your to-do

    Do this now

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done