A minimal, production-ready Solidity boilerplate featuring upgradeable smart contracts with the TransparentProxy pattern.
This boilerplate provides a simple Counter example demonstrating:
- ✅ Upgradeable contracts using OpenZeppelin's TransparentUpgradeableProxy
- ✅ Separation of concerns (Storage, Logic, Lens)
- ✅ User-specific counters with global statistics
- ✅ Comprehensive test suite with upgrade testing
- ✅ Deployment and upgrade scripts
Counter System
├── Counter (Abstract) - Main logic contract
├── CounterInstance - Concrete implementation
├── CounterStorage - Storage layout
├── CounterLens - Gas-efficient read queries
└── CounterV2 - Example upgrade (adds decrement)
Counter.sol (Upgradeable)
increment()- Increment caller's counter by 1- Tracks total increments and unique users
- Owner-controlled via OwnableUpgradeable
CounterLens.sol (Non-upgradeable)
getCount()- Get user's counter valuegetUserStats()- Get user statisticsgetGlobalStats()- Get global statisticsgetCountBatch()- Batch query multiple users
# Install Foundry
curl -L https://foundry.paradigm.xyz | bash
foundryup
# Clone the repository
git clone <your-repo>
cd solidity-contract-boilerplate
# Install dependencies
forge installforge build# Run all tests
forge test
# Run with verbosity
forge test -vvv
# Run specific test file
forge test --match-path tests/Counter.t.sol
# Run with gas reporting
forge test --gas-report# Local deployment (Anvil)
anvil
# In another terminal
forge script scripts/Deploy.s.sol:DeployTransparentScript --rpc-url http://localhost:8545 --broadcast --private-key <PRIVATE_KEY>
# Deploy to testnet (e.g., Sepolia)
forge script scripts/Deploy.s.sol:DeployTransparentScript --rpc-url $ETH_SEPOLIA_RPC_URL --broadcast --verify# Upgrade Counter implementation
forge script scripts/UpgradeCounter.s.sol:UpgradeCounterScript --rpc-url <RPC_URL> --broadcast├── src/
│ ├── Counter.sol # Main upgradeable contract
│ ├── CounterInstance.sol # Concrete implementation
│ ├── CounterStorage.sol # Storage layout
│ ├── CounterLens.sol # Read-only queries
│ ├── CounterV2.sol # Example upgrade
│ ├── CounterInstanceV2.sol # V2 implementation
│ ├── interfaces/
│ │ ├── ICounter.sol # Counter interface
│ │ └── ICounterLens.sol # Lens interface
│ └── mocks/
│ └── MockUSDC.sol # Mock ERC20 for testing
├── scripts/
│ ├── Deploy.s.sol # Deployment script
│ └── UpgradeCounter.s.sol # Upgrade script
├── tests/
│ ├── Counter.t.sol # Counter tests
│ ├── CounterLens.t.sol # Lens tests
│ └── CounterUpgrade.t.sol # Upgrade tests
├── foundry.toml # Foundry configuration
└── README.md
- Counter.t.sol: Basic functionality, events, multi-user scenarios
- CounterLens.t.sol: All read functions, batch queries, integration tests
- CounterUpgrade.t.sol: State preservation, new functionality, downgrade scenarios
# Generate coverage report
forge coverageThis boilerplate demonstrates the TransparentProxy upgrade pattern:
- Initial Deployment: Deploy implementation → Deploy proxy → Initialize
- Upgrade: Deploy new implementation → Call
upgradeAndCall() - State Preservation: All storage variables remain intact
V1 Features:
increment()- Increment counter
V2 Features (Added):
decrementBy(uint256)- Decrement counter by amountversion()- Returns version string
See tests/CounterUpgrade.t.sol for complete upgrade testing examples.
The boilerplate follows gas optimization best practices:
- ✅ Packed storage slots (CounterStorage)
- ✅ Separate read contract (Lens) to reduce proxy overhead
- ✅ Efficient mappings for user data
- ✅ Events for off-chain indexing
- ✅ OpenZeppelin battle-tested contracts
- ✅ Initializer protection with
_disableInitializers() - ✅ Owner-only upgrade mechanism via ProxyAdmin
- ✅ Comprehensive test coverage
- ✅ Storage gap for future upgrades
- Modify
src/Counter.solwith your business logic - Update
src/CounterStorage.solwith your state variables - Adjust
src/CounterLens.solfor your read queries - Update tests and interfaces accordingly
- Keep storage layout in separate contract
- Maintain storage gap for future upgrades
- Use abstract/concrete pattern for implementation
- Separate write (upgradeable) and read (non-upgradeable) contracts
Create a .env file:
PRIVATE_KEY=your_private_key
ETH_SEPOLIA_RPC_URL=your_sepolia_rpc
ETH_SEPOLIA_SCAN_API_KEY=your_etherscan_apiContributions are welcome! Please follow these guidelines:
- Fork the repository
- Create a feature branch
- Write tests for new functionality
- Ensure all tests pass
- Submit a pull request
MIT License - see LICENSE file for details
- Foundry Book
- OpenZeppelin Upgradeable Contracts
- Solidity Documentation
- EIP-1967: Proxy Storage Slots
For questions and support, please open an issue in the GitHub repository.
Built with ❤️ using Foundry