Hey everyone π
Welcome to Day 11 of my #30DaysOfSolidity challenge!
Today, weβll build a Secure Vault contract where only the owner (master key holder) can control the funds.
This project demonstrates Solidity inheritance, access control, and security best practices for managing ETH securely β just like real-world DeFi vaults.
π§© What We'll Build
We'll split our logic into two contracts:
- Ownable β handles ownership and access control.
-
VaultMaster β inherits
Ownable
and manages Ether deposits and withdrawals.
This modular design makes your code reusable, secure, and easy to maintain β following production-level smart contract architecture.
ποΈ Project Structure
Hereβs how the folder structure looks:
day-11-vault/
β
βββ contracts/
β βββ Ownable.sol
β βββ VaultMaster.sol
β
βββ scripts/
β βββ deploy.js
β
βββ test/
β βββ vaultmaster.test.js
β
βββ hardhat.config.js
βββ package.json
βοΈ Contract 1: Ownable.sol
The Ownable
contract manages ownership and provides the onlyOwner
modifier used for access control.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
/// @title Ownable - Lightweight ownership control base contract
/// @notice Provides an `onlyOwner` modifier and ownership transfer functionality
contract Ownable {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
constructor() {
_owner = msg.sender;
emit OwnershipTransferred(address(0), _owner);
}
function owner() public view returns (address) {
return _owner;
}
modifier onlyOwner() {
require(msg.sender == _owner, "Ownable: caller is not the owner");
_;
}
function transferOwnership(address newOwner) public onlyOwner {
require(newOwner != address(0), "Ownable: new owner is zero address");
emit OwnershipTransferred(_owner, newOwner);
_owner = newOwner;
}
function renounceOwnership() public onlyOwner {
emit OwnershipTransferred(_owner, address(0));
_owner = address(0);
}
}
π° Contract 2: VaultMaster.sol
This contract acts as our digital safe. It allows anyone to deposit ETH, but only the owner can withdraw or transfer ownership.
It also includes:
- Reentrancy protection
- Event logging
- Balance visibility
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "./Ownable.sol";
/// @title VaultMaster - Secure vault controlled by the contract owner
/// @notice Allows only the owner to withdraw or transfer ownership
contract VaultMaster is Ownable {
uint256 private _status;
uint256 private constant _NOT_ENTERED = 1;
uint256 private constant _ENTERED = 2;
event Deposited(address indexed sender, uint256 amount);
event Withdrawn(address indexed to, uint256 amount);
constructor() {
_status = _NOT_ENTERED;
}
modifier nonReentrant() {
require(_status == _NOT_ENTERED, "VaultMaster: reentrant call");
_status = _ENTERED;
_;
_status = _NOT_ENTERED;
}
receive() external payable {
require(msg.value > 0, "VaultMaster: no ETH sent");
emit Deposited(msg.sender, msg.value);
}
function deposit() external payable {
require(msg.value > 0, "VaultMaster: no ETH sent");
emit Deposited(msg.sender, msg.value);
}
function withdraw(uint256 amount, address payable to) external onlyOwner nonReentrant {
require(to != address(0), "VaultMaster: zero address");
require(address(this).balance >= amount, "VaultMaster: insufficient balance");
(bool sent, ) = to.call{value: amount}("");
require(sent, "VaultMaster: transfer failed");
emit Withdrawn(to, amount);
}
function withdrawAll(address payable to) external onlyOwner nonReentrant {
require(to != address(0), "VaultMaster: zero address");
uint256 bal = address(this).balance;
require(bal > 0, "VaultMaster: empty vault");
(bool sent, ) = to.call{value: bal}("");
require(sent, "VaultMaster: transfer failed");
emit Withdrawn(to, bal);
}
function getBalance() external view returns (uint256) {
return address(this).balance;
}
}
π§ͺ Test Script β vaultmaster.test.js
Hereβs a sample Hardhat test to verify deposits, withdrawals, and access control.
const { expect } = require("chai");
const { ethers } = require("hardhat");
describe("VaultMaster", function () {
let owner, alice, vault;
beforeEach(async () => {
[owner, alice] = await ethers.getSigners();
const VaultMaster = await ethers.getContractFactory("VaultMaster");
vault = await VaultMaster.deploy();
await vault.deployed();
});
it("accepts deposits", async () => {
await alice.sendTransaction({ to: vault.address, value: ethers.utils.parseEther("1") });
const balance = await ethers.provider.getBalance(vault.address);
expect(balance).to.equal(ethers.utils.parseEther("1"));
});
it("only owner can withdraw", async () => {
await alice.sendTransaction({ to: vault.address, value: ethers.utils.parseEther("1") });
await expect(vault.connect(alice).withdraw(ethers.utils.parseEther("1"), alice.address))
.to.be.revertedWith("Ownable: caller is not the owner");
});
});
π Deployment Script β deploy.js
const { ethers } = require("hardhat");
async function main() {
const VaultMaster = await ethers.getContractFactory("VaultMaster");
const vault = await VaultMaster.deploy();
await vault.deployed();
console.log("VaultMaster deployed to:", vault.address);
}
main();
π‘ How It Works
-
Deploy the
VaultMaster
contract β deployer becomes the owner automatically. - Deposit ETH β anyone can send Ether to the contract.
- Withdraw ETH β only the owner can withdraw or transfer ownership.
- Reentrancy guard ensures no malicious multiple withdrawals.
π Security Highlights
-
onlyOwner
modifier restricts sensitive functions. -
nonReentrant
modifier prevents reentrancy attacks. - Events make transactions auditable on-chain.
- Ownership can be safely transferred or renounced.
π§ Real-World Use Cases
- DAO or company treasuries.
- Multi-sig vaults with additional governance.
- Secure smart contract fund storage.
- DeFi liquidity safes or staking pools.
π§ Future Enhancements
- Add ERC20 token support.
- Add multi-sig or time-lock functionality.
- Integrate OpenZeppelinβs audited libraries for production.
- Build a frontend dashboard for deposits and withdrawals.
π Conclusion
In this project, we explored:
- Solidity inheritance and access control
- Writing modular and secure contracts
- Building a Vault that follows production-grade patterns
A small yet powerful step toward writing secure Web3 smart contracts.
π¬ What do you think of todayβs project?
Drop your thoughts and feedback in the comments π
π Read More β #30DaysOfSolidity Series by Saurav Kumar
Top comments (0)