Your First Smart Contract
Lesson by Uvin Vindula
You've learned Solidity basics and set up your development environment. Now it's time to put it all together. In this lesson, you'll write, test, and deploy a simple storage smart contract — taking it from code to a live contract on a test network. This is the fundamental workflow that every Web3 developer uses, from beginners to professionals building billion-dollar protocols.
The Contract: Enhanced Storage
We'll build a slightly enhanced version of the SimpleStorage contract from Lesson 2. This version adds an event log, access control, and a history of stored values:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract EnhancedStorage {
// State variables
address public owner;
uint256 public currentValue;
uint256 public updateCount;
uint256[] public valueHistory;
// Events
event ValueUpdated(
address indexed updater,
uint256 oldValue,
uint256 newValue,
uint256 timestamp
);
// Constructor
constructor(uint256 _initialValue) {
owner = msg.sender;
currentValue = _initialValue;
valueHistory.push(_initialValue);
}
// Store a new value
function store(uint256 _newValue) public {
require(_newValue != currentValue, "Same value");
uint256 oldValue = currentValue;
currentValue = _newValue;
updateCount++;
valueHistory.push(_newValue);
emit ValueUpdated(msg.sender, oldValue, _newValue, block.timestamp);
}
// Get full history
function getHistory() public view returns (uint256[] memory) {
return valueHistory;
}
// Get history length
function getHistoryLength() public view returns (uint256) {
return valueHistory.length;
}
}
Step 1: Create the Contract File
In your Hardhat project directory, create a new file at contracts/EnhancedStorage.sol and paste the contract code above.
Step 2: Compile
Run the Solidity compiler to check for errors:
npx hardhat compile
If successful, you'll see "Compiled 1 Solidity file successfully." If there are errors, the compiler will tell you exactly which line has the problem.
Step 3: Write Tests
Testing is non-negotiable in smart contract development. Unlike Web2 bugs that can be patched, smart contract bugs on mainnet are permanent. Create a test file at test/EnhancedStorage.test.js:
const { expect } = require("chai");
const { ethers } = require("hardhat");
describe("EnhancedStorage", function () {
let storage;
let owner;
let otherUser;
beforeEach(async function () {
[owner, otherUser] = await ethers.getSigners();
const StorageFactory = await ethers.getContractFactory("EnhancedStorage");
storage = await StorageFactory.deploy(42); // Deploy with initial value 42
await storage.waitForDeployment();
});
it("should set the initial value correctly", async function () {
expect(await storage.currentValue()).to.equal(42);
});
it("should update the value and increment count", async function () {
await storage.store(100);
expect(await storage.currentValue()).to.equal(100);
expect(await storage.updateCount()).to.equal(1);
});
it("should maintain value history", async function () {
await storage.store(100);
await storage.store(200);
const history = await storage.getHistory();
expect(history.length).to.equal(3); // 42, 100, 200
});
it("should reject same value", async function () {
await expect(storage.store(42)).to.be.revertedWith("Same value");
});
it("should emit ValueUpdated event", async function () {
await expect(storage.store(100))
.to.emit(storage, "ValueUpdated")
.withArgs(owner.address, 42, 100, await getBlockTimestamp());
});
});
Run tests with:
npx hardhat test
All tests should pass. If any fail, the output tells you exactly what went wrong. Fix the issue and run tests again.
Step 4: Deploy Locally
Create a deployment script at scripts/deploy.js:
const { ethers } = require("hardhat");
async function main() {
const initialValue = 42;
console.log("Deploying EnhancedStorage with initial value:", initialValue);
const StorageFactory = await ethers.getContractFactory("EnhancedStorage");
const storage = await StorageFactory.deploy(initialValue);
await storage.waitForDeployment();
const address = await storage.getAddress();
console.log("EnhancedStorage deployed to:", address);
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
Deploy to the local Hardhat network:
npx hardhat run scripts/deploy.js
Step 5: Deploy to Sepolia Testnet
To deploy to a real test network, update your hardhat.config.js with the Sepolia network configuration:
require("@nomicfoundation/hardhat-toolbox");
module.exports = {
solidity: "0.8.19",
networks: {
sepolia: {
url: "YOUR_ALCHEMY_OR_INFURA_SEPOLIA_URL",
accounts: ["YOUR_DEVELOPMENT_WALLET_PRIVATE_KEY"]
}
}
};
.env file (with dotenv package) and add .env to your .gitignore. Exposing a private key — even for a testnet wallet — is a dangerous habit. ALWAYS use a development-only wallet with no real funds.
Deploy to Sepolia:
npx hardhat run scripts/deploy.js --network sepolia
After deployment, you'll see a contract address. Visit sepolia.etherscan.io and search for that address — you'll see your contract live on the blockchain. You can verify the source code on Etherscan so anyone can read and interact with your contract.
What You've Accomplished
Congratulations — you've just completed the full smart contract development lifecycle:
- Wrote a Solidity smart contract with state variables, functions, events, and input validation
- Compiled it using the Hardhat Solidity compiler
- Wrote and ran automated tests to verify correct behavior
- Deployed to a local development blockchain
- Deployed to a public testnet (Sepolia)
This is the exact workflow used by every smart contract developer in the industry. The contracts get more complex, the tests get more thorough, and the stakes get higher — but the process remains the same. You now have the foundation to build on.
Key Takeaways
- •The smart contract development lifecycle: write Solidity, compile with Hardhat, test thoroughly, deploy locally, deploy to testnet, then (optionally) to mainnet
- •Testing is non-negotiable — unlike Web2 bugs, smart contract bugs on mainnet are permanent and can result in irreversible fund loss
- •NEVER commit private keys to version control — use environment variables and .env files, and always use development-only wallets
- •Deploying to Sepolia testnet gives you a live, verifiable contract on a public blockchain at zero cost
- •This workflow is identical to what professional Web3 developers use — the contracts get more complex, but the process remains the same
Quick Quiz
Question 1 of 3
0 correct so far
Why is testing considered "non-negotiable" in smart contract development?