DEV Community

Cover image for 🏰 Day 20 of #30DaysOfSolidity β€” Build a Secure Digital Vault (FortKnox)
Saurav Kumar
Saurav Kumar

Posted on

🏰 Day 20 of #30DaysOfSolidity β€” Build a Secure Digital Vault (FortKnox)

πŸ’‘ Introduction

Welcome to Day 20 of #30DaysOfSolidity!
Today, we’re tackling one of the most critical smart contract security topics β€” Reentrancy Attacks πŸ”

We’ll build a Secure Digital Vault, where users can deposit and withdraw tokenized gold (or any ERC-20 asset) safely β€” just like a decentralized version of Fort Knox.

This project demonstrates how a simple withdrawal function can become a security vulnerability, and how to fix it using:

  • The nonReentrant modifier
  • The Checks-Effects-Interactions (CEI) pattern
  • OpenZeppelin’s SafeERC20 for safe token transfers

🧠 What Is a Reentrancy Attack?

A reentrancy attack occurs when a malicious contract repeatedly calls a vulnerable function before the previous execution finishes.

If the contract updates user balances after sending tokens, attackers can exploit that gap to withdraw multiple times before the balance changes β€” draining all funds. 😱


πŸ›‘οΈ Our Defense: The FortKnox Pattern

To make our vault impenetrable, we use two layers of protection:

🧱 1. nonReentrant Modifier

A mutex (lock) prevents re-entry into functions already being executed.
If one transaction is in progress, no nested calls can occur until it finishes.

βš™οΈ 2. Checks-Effects-Interactions (CEI) Pattern

Always follow this safe order:

  1. βœ… Check: Validate conditions
  2. βš™οΈ Effect: Update state (like user balance)
  3. πŸ”— Interact: Finally, transfer tokens or call external contracts

This pattern minimizes risk even if nonReentrant were misused.


🧩 File Structure

day-20-fortknox/
β”œβ”€β”€ contracts/
β”‚   └── FortKnoxVault.sol
β”œβ”€β”€ test/
β”‚   └── FortKnoxVault.t.sol
β”œβ”€β”€ scripts/
β”‚   └── deploy.js
β”œβ”€β”€ foundry.toml
└── README.md
Enter fullscreen mode Exit fullscreen mode

πŸ’Ύ Source Code β€” FortKnoxVault.sol

Here’s the complete smart contract, written in Solidity 0.8.19, using OpenZeppelin for safety and ownership control.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

/// @title FortKnoxVault β€” A secure digital vault for tokenized gold
/// @notice Protects against reentrancy using a nonReentrant modifier
contract FortKnoxVault is Ownable {
    using SafeERC20 for IERC20;

    IERC20 public immutable token;

    mapping(address => uint256) private balances;

    uint8 private locked;
    bool public paused;

    event Deposited(address indexed user, uint256 amount);
    event Withdrawn(address indexed user, uint256 amount);
    event Paused(address indexed account);
    event Unpaused(address indexed account);

    constructor(IERC20 _token) {
        token = _token;
        locked = 0;
        paused = false;
    }

    /// @dev Prevents reentrancy (custom mutex pattern)
    modifier nonReentrant() {
        require(locked == 0, "FortKnox: reentrant call");
        locked = 1;
        _;
        locked = 0;
    }

    modifier whenNotPaused() {
        require(!paused, "FortKnox: paused");
        _;
    }

    /// @notice Deposit ERC20 tokens into the vault
    function deposit(uint256 amount) external whenNotPaused {
        require(amount > 0, "FortKnox: zero deposit");
        balances[msg.sender] += amount;

        // Pull tokens from user
        token.safeTransferFrom(msg.sender, address(this), amount);

        emit Deposited(msg.sender, amount);
    }

    /// @notice Withdraw your tokens safely (reentrancy protected)
    function withdraw(uint256 amount) external nonReentrant whenNotPaused {
        require(amount > 0, "FortKnox: zero withdraw");
        uint256 bal = balances[msg.sender];
        require(bal >= amount, "FortKnox: insufficient balance");

        // Effects before interactions
        balances[msg.sender] = bal - amount;

        // Safe external call
        token.safeTransfer(msg.sender, amount);

        emit Withdrawn(msg.sender, amount);
    }

    function balanceOf(address user) external view returns (uint256) {
        return balances[user];
    }

    // Admin controls
    function pause() external onlyOwner {
        paused = true;
        emit Paused(msg.sender);
    }

    function unpause() external onlyOwner {
        paused = false;
        emit Unpaused(msg.sender);
    }

    /// @notice Rescue stuck tokens (only owner)
    function rescueTokens(address to, uint256 amount) external onlyOwner {
        require(to != address(0), "FortKnox: zero address");
        token.safeTransfer(to, amount);
    }
}
Enter fullscreen mode Exit fullscreen mode

πŸ§ͺ Testing Scenarios

To ensure full security, test the following cases:

Test Case Expected Behavior
βœ… Deposit and withdraw Works normally
🚫 Withdraw twice (reentrancy attempt) Reverts immediately
πŸ›‘ Vault paused Deposit/withdraw disabled
⚠️ Withdraw more than balance Reverts safely
🧰 Admin rescue Only owner can execute

You can use Foundry, Hardhat, or Remix for testing.
In Foundry, try simulating a malicious attacker contract calling withdraw() recursively β€” it will fail due to the nonReentrant lock πŸ”’.


🧩 Front-End Demo (Ethers.js + React)

Here’s a simple snippet to interact with the vault using Ethers.js:

import { ethers } from "ethers";
import FortKnoxVaultABI from "./FortKnoxVault.json";

const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();
const vault = new ethers.Contract(vaultAddress, FortKnoxVaultABI, signer);

// Deposit tokenized gold
const deposit = async (amount) => {
  const token = new ethers.Contract(tokenAddress, tokenABI, signer);
  await token.approve(vaultAddress, amount);
  await vault.deposit(amount);
};

// Withdraw safely
const withdraw = async (amount) => {
  await vault.withdraw(amount);
};
Enter fullscreen mode Exit fullscreen mode

This front-end connects MetaMask, allows token deposits, and ensures only the rightful user can withdraw their funds β€” all secured from reentrancy attacks.


πŸ”’ Security Practices Used

  1. nonReentrant β€” Prevents nested calls.
  2. Checks-Effects-Interactions β€” Updates state before transfers.
  3. SafeERC20 β€” Handles non-standard tokens safely.
  4. Pause mechanism β€” Allows emergency stop.
  5. Owner-only rescue β€” Protects from misconfigurations.
  6. No loops β€” Avoids gas griefing & DoS risks.
  7. Event logging β€” For transparency and audits.

🧠 Real-World Analogy

Think of this as a digital Fort Knox πŸ’°

  • Everyone can deposit their gold (tokens).
  • Withdrawals are strictly controlled.
  • No one can open the vault door twice at once β€” thanks to nonReentrant.
  • The books (balances) are updated before the gold moves β€” CEI pattern at work.

βš™οΈ Before Mainnet Deployment

βœ… Run malicious reentrancy tests
βœ… Use audited OpenZeppelin dependencies
βœ… Add multisig for owner functions
βœ… Verify on Etherscan or Polygonscan
βœ… Consider time delays for rescue functions


πŸš€ Future Enhancements

  • Support EIP-2612 permit() deposits (gasless approvals)
  • Add timelocked withdrawals for long-term vaults
  • Use Chainlink proof-of-reserve for transparency
  • Create a dashboard UI for deposits and balances

πŸ“˜ Summary

The FortKnoxVault demonstrates how a seemingly simple withdrawal function can open the door to reentrancy β€” and how to close it securely.

By applying the nonReentrant modifier and Checks-Effects-Interactions, you can write secure, production-grade DeFi contracts that protect user funds and build trust.

Remember: in smart contracts, security is not optional β€” it’s the foundation of trust.


🧱 Learning Outcome

After this project, you’ll understand:

  • How reentrancy works and how to stop it
  • How to design safe withdrawal logic
  • How to apply Solidity’s security best practices
  • How to test for common vulnerabilities

🧩 #30DaysOfSolidity Recap

Day 19: Signature-Based Authentication
Day 20: Secure Digital Vault (FortKnox)
Day 21: Create Your Own Digital Collectibles (NFTs) πŸš€


πŸ”— Connect With Me

πŸ’¬ Dev.to: @sauravkumar8178
🐦 Twitter (X): @sauravk8178
πŸ’Ό LinkedIn: Saurav Kumar

Top comments (0)