Project: SimpleToken
Test Framework: Hardhat + Chai + Ethers.js
Total Tests: 47
Status: ✅ All Passing
Last Run: February 2026
Test Suites: 1 passed, 1 total
Tests: 47 passed, 47 total
Time: ~2 seconds
Coverage: 100% of public functions
| Category | Tests | Coverage |
|---|---|---|
| Deployment | 5 | 100% |
| Basic Transfers | 6 | 100% |
| Transfer Fees | 7 | 100% |
| Whitelist Management | 7 | 100% |
| Admin Functions | 9 | 100% |
| Pausable Functionality | 4 | 100% |
| View Functions | 6 | 100% |
| Edge Cases & Security | 3 | 100% |
✓ Should set the correct token name and symbol
✓ Should mint initial supply to deployer
✓ Should set deployer as owner
✓ Should initialize with 2% fee (200 basis points)
✓ Should add deployer to whitelist automaticallyWhat's Tested:
- Token initialization (name, symbol, decimals)
- Initial supply minting
- Ownership assignment
- Default fee configuration
- Automatic whitelist addition
Coverage: ✅ 100%
✓ Should transfer tokens between accounts
✓ Should fail when sender doesn't have enough tokens
✓ Should update balances after transfers
✓ Should emit Transfer event
✓ Should handle approve and transferFrom
✓ Should fail transferFrom without approvalWhat's Tested:
- Standard ERC20 transfer functionality
- Balance validation
- Event emission
- Approval mechanism
- Authorization checks
Coverage: ✅ 100%
✓ Should deduct 2% fee on transfers
✓ Should burn the fee (reduce total supply)
✓ Should emit FeeBurned event
✓ Should calculate fee correctly for different amounts
✓ Should track totalFeesBurned
✓ Should not charge fee if sender is whitelisted
✓ Should not charge fee if recipient is whitelistedWhat's Tested:
- Fee calculation (2% = 200 basis points)
- Burn mechanism
- Total supply reduction
- Event emission
- Fee tracking
- Whitelist exemption (sender)
- Whitelist exemption (recipient)
Test Example:
// Transfer 1000 tokens with 2% fee
const amount = ethers.parseEther("1000");
await token.connect(addr1).transfer(addr2.address, amount);
// Expected:
// Fee: 20 tokens (2%)
// Received: 980 tokens
// Burned: 20 tokens
// Total supply: -20 tokensCoverage: ✅ 100%
✓ Should allow owner to add address to whitelist
✓ Should allow owner to remove address from whitelist
✓ Should emit WhitelistAdded event
✓ Should emit WhitelistRemoved event
✓ Should not allow non-owner to add to whitelist
✓ Should not allow non-owner to remove from whitelist
✓ Should correctly check whitelist statusWhat's Tested:
- Add whitelist functionality
- Remove whitelist functionality
- Event emission
- Access control (onlyOwner)
- Whitelist query function
Security Checks:
- ❌ Non-owner cannot add to whitelist
- ❌ Non-owner cannot remove from whitelist
- ✅ Only owner has whitelist control
Coverage: ✅ 100%
✓ Should allow owner to change fee percentage
✓ Should emit FeePercentChanged event
✓ Should not allow fee above MAX_FEE_PERCENT (10%)
✓ Should not allow non-owner to change fee
✓ Should allow owner to set max transaction amount
✓ Should emit MaxTransactionAmountSet event
✓ Should reject transactions above max amount
✓ Should allow unlimited transactions when max is 0
✓ Should not allow non-owner to set max transaction amountWhat's Tested:
- Fee percentage changes (0-1000 basis points)
- Fee validation (max 10%)
- Event emission
- Access control
- Max transaction limit setting
- Transaction amount validation
- Unlimited mode (max = 0)
Boundary Tests:
// Valid: Set fee to 5%
await token.setFeePercent(500);
// Invalid: Set fee to 15% (exceeds MAX_FEE_PERCENT)
await expect(token.setFeePercent(1500))
.to.be.revertedWithCustomError(token, "FeePercentTooHigh");
// Valid: Disable max limit
await token.setMaxTransactionAmount(0);
// Invalid: Transfer above max
await expect(token.transfer(addr2, tooMuchAmount))
.to.be.revertedWithCustomError(token, "MaxTransactionExceeded");Coverage: ✅ 100%
✓ Should allow owner to pause transfers
✓ Should prevent transfers when paused
✓ Should allow owner to unpause
✓ Should not allow non-owner to pauseWhat's Tested:
- Pause mechanism
- Transfer blocking when paused
- Unpause mechanism
- Access control
Security Validation:
// Pause contract
await token.pause();
// Try to transfer (should fail)
await expect(token.transfer(addr2, amount))
.to.be.revertedWithCustomError(token, "EnforcedPause");
// Unpause
await token.unpause();
// Transfer works again
await token.transfer(addr2, amount);Coverage: ✅ 100%
✓ Should calculate fee correctly
✓ Should return whitelist status
✓ Should return contract info
✓ Should return correct token metadata
✓ Should return accurate total supply
✓ Should track fees burned correctlyWhat's Tested:
calculateFee()accuracyisWhitelisted()querygetContractInfo()comprehensive data- Token metadata (name, symbol, decimals)
- Total supply tracking
- Burned fees tracking
Test Example:
const amount = ethers.parseEther("1000");
const [fee, transferAmount] = await token.calculateFee(amount);
// With 2% fee:
// fee = 20 tokens
// transferAmount = 980 tokens
expect(fee).to.equal(ethers.parseEther("20"));
expect(transferAmount).to.equal(ethers.parseEther("980"));Coverage: ✅ 100%
✓ Should handle zero amount transfers correctly
✓ Should prevent transfer to zero address
✓ Should handle maximum uint256 amounts safelyWhat's Tested:
- Zero amount validation
- Zero address protection
- Integer overflow protection (Solidity 0.8+)
- Large number handling
Security Validations:
// Zero amount
await expect(token.transfer(addr2, 0))
.to.be.revertedWithCustomError(token, "ZeroAmount");
// Zero address
await expect(token.transfer(ethers.ZeroAddress, amount))
.to.be.revertedWithCustomError(token, "InvalidAddress");
// Large amounts (no overflow in Solidity 0.8+)
const maxUint = ethers.MaxUint256;
// Automatically reverts if overflow occursCoverage: ✅ 100%
| Function | Gas Used | Optimization Level |
|---|---|---|
transfer() |
~65,000 | ✅ Good |
transferFrom() |
~75,000 | ✅ Good |
approve() |
~46,000 | ✅ Excellent |
setFeePercent() |
~45,000 | ✅ Excellent |
addToWhitelist() |
~48,000 | ✅ Excellent |
pause() |
~28,000 | ✅ Excellent |
Comparison with Industry:
- USDT transfer: ~60,000 gas
- USDC transfer: ~55,000 gas
- Uniswap swap: ~120,000 gas
- SimpleToken transfer: ~65,000 gas ✅
Verdict: Gas costs are competitive for a token with fee mechanism.
File | % Stmts | % Branch | % Funcs | % Lines |
--------------------|---------|----------|---------|---------|
contracts/ | 100.00 | 95.00 | 100.00 | 100.00 |
SimpleToken.sol | 100.00 | 95.00 | 100.00 | 100.00 |
--------------------|---------|----------|---------|---------|
All files | 100.00 | 95.00 | 100.00 | 100.00 |
Note: Branch coverage at 95% because some error conditions (overflow, underflow) are automatically handled by Solidity 0.8+ and difficult to test explicitly.
-
Happy Paths
- All core functionality works as expected
- Standard ERC20 operations
-
Error Conditions
- Invalid inputs rejected
- Custom errors properly thrown
- Access control enforced
-
Edge Cases
- Zero amounts
- Zero addresses
- Maximum values
- Boundary conditions
-
State Changes
- Balances updated correctly
- Events emitted properly
- Storage variables tracked accurately
-
Integration
- Multiple functions working together
- Complex scenarios (whitelist + fee + pause)
-
Reentrancy Attacks
- Requires malicious contract
- Not applicable (no external calls to untrusted contracts)
-
Front-Running
- Requires mempool simulation
- Testing framework limitation
-
Gas Limit DoS
- Requires block gas limit manipulation
- Testing framework limitation
-
Real Network Conditions
- Network congestion
- MEV bot interactions
- Testnet deployment recommended
-
Long-Term Economics
- Burn rate over time
- Supply dynamics
- Requires simulation tools (separate project)
npx hardhat testnpx hardhat test --verbose# Add to hardhat.config.js:
gasReporter: {
enabled: true,
currency: 'USD',
coinmarketcap: process.env.COINMARKETCAP_API_KEY
}
# Run tests with gas report
REPORT_GAS=true npx hardhat test# Install coverage plugin
npm install --save-dev solidity-coverage
# Run coverage
npx hardhat coverage# .github/workflows/test.yml
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '22'
- run: npm ci
- run: npx hardhat compile
- run: npx hardhat test-
After Bug Fixes
- Add test that would have caught the bug
- Prevent regression
-
Before New Features
- Write test first (TDD)
- Ensure new code is tested
-
After Security Reviews
- Add tests for identified edge cases
- Cover attack scenarios
-
Integration Testing
- Test with actual DEX contracts (Uniswap)
- Test with multi-sig wallets
-
Fuzzing Tests
# Use Echidna or Foundry echidna . --contract SimpleToken --config echidna.yaml
-
Formal Verification
# Use Certora Prover certoraRun SimpleToken --verify SimpleToken:spec/SimpleToken.spec -
Testnet Battle-Testing
- Deploy to Sepolia
- Run for 30+ days
- Monitor all transactions
- Stress test with high volume
-
Integration Tests
- Test with Uniswap contracts
- Test with Gnosis Safe
- Test with common DeFi protocols
-
Economic Simulation
- Model burn rate over time
- Simulate various trading patterns
- Analyze equilibrium states
Test Quality: ✅ Excellent for educational project
Production Readiness:
This test suite demonstrates:
- ✅ Comprehensive coverage of all public functions
- ✅ Security-conscious testing (access control, edge cases)
- ✅ Proper use of modern testing practices (custom errors, events)
- ✅ Clear test organization and naming
- ✅ Good balance of positive and negative test cases
For Portfolio/Interview: This level of testing shows strong understanding of smart contract development and security considerations.
For Production: Combine with professional audit, fuzzing, and extended testnet deployment.
Next Steps:
- Run tests:
npx hardhat test - Review test file:
test/SimpleToken.test.js - Add gas reporter (optional)
- Deploy to testnet and test manually
- Prepare for audit (see SECURITY.md)
This document complements the main README.md and SECURITY.md files.