A Foundry-based ERC-20 staking dApp where users lock a fixed token amount and claim ETH rewards after a configurable period.
StakingApp is a learning-oriented smart contract project that demonstrates token staking with time-locked ETH rewards. Users deposit a predefined ERC-20 amount, wait for the staking period to elapse, and claim ETH funded by the contract owner. The owner can fund the contract, adjust the staking period, and manage access through OpenZeppelin's Ownable.
Key features:
- ๐ Fixed-amount ERC-20 deposits โ one active stake per user
- โฑ๏ธ Configurable staking period with multi-period reward accrual
- ๐ฐ ETH reward claims after the lock period
- ๐ค Owner-controlled funding via
receive()and period updates - ๐ก๏ธ
SafeERC20transfers and Checks-Effects-Interactions (CEI) on withdrawals - โ Full unit test suite (15 tests) covering deposit, withdraw, rewards, and access control
- Prerequisites & Dependencies
- Technologies & Versions
- Project Structure
- Quick Start
- Running Tests
- Cache & Architecture
- Security Policy
- Scripts & Commands
- License
- About the Author
| Requirement | Notes |
|---|---|
| ๐ฅ๏ธ OS | macOS, Linux, or Windows (WSL recommended) |
| ๐ฆ Foundry | Install Foundry โ includes forge, cast, and anvil |
| ๐ง Git | Required for cloning and managing submodules |
Install Foundry:
curl -L https://foundry.paradigm.xyz | bash
foundryupVerify the installation:
forge --versionDependencies are managed as Git submodules under lib/ and pinned in foundry.lock.
| Dependency | Version | Purpose |
|---|---|---|
| OpenZeppelin Contracts | v5.6.1 |
ERC20, Ownable, SafeERC20 |
| forge-std | v1.16.1 |
Foundry testing utilities and cheatcodes |
Warning
When installing OpenZeppelin via git, avoid tracking the master branch. Use tagged releases (for example @v5.6.1) so builds stay reproducible. See the OpenZeppelin Foundry installation notes.
Install or update dependencies explicitly:
forge install foundry-rs/forge-std@v1.16.1
forge install OpenZeppelin/openzeppelin-contracts@v5.6.1| Technology | Version | Role |
|---|---|---|
| Solidity | 0.8.35 |
Smart contract language |
| Foundry (forge) | 1.7.1+ |
Build, test, and deploy toolchain |
| OpenZeppelin Contracts | v5.6.1 |
Battle-tested contract libraries |
| forge-std | v1.16.1 |
Test helpers, cheatcodes, assertions |
| Git submodules | โ | Dependency management via lib/ |
StakingApp/
โโโ ๐ src/
โ โโโ StakingApp.sol # Main staking logic: deposit, withdraw, claim rewards
โ โโโ StakingAppv1.sol # Draft / alternate implementation (WIP)
โ โโโ StakingToken.sol # Mintable ERC-20 token used for staking
โโโ ๐ test/
โ โโโ StakingAppTest.t.sol # StakingApp unit tests (14 tests)
โ โโโ StakingTokenTest.t.sol # StakingToken unit tests (1 test)
โโโ ๐ lib/
โ โโโ forge-std/ # Foundry standard library (submodule)
โ โโโ openzeppelin-contracts/ # OpenZeppelin contracts (submodule)
โโโ ๐ cache/ # Foundry compilation cache (auto-generated)
โโโ ๐ out/ # Compiled artifacts & ABIs (auto-generated)
โโโ foundry.toml # Foundry project configuration
โโโ foundry.lock # Pinned dependency versions
โโโ README.mdgit clone https://github.com/noelialuz/StakingApp.git
cd StakingAppforge installIf submodules are missing, run the explicit install commands from the Prerequisites section.
forge buildA successful build produces artifacts in out/ and updates the cache in cache/.
forge testforge fmtCheck formatting without modifying files:
forge fmt --checkDeploy StakingToken and StakingApp, then interact with the staking contract:
// 1. Deploy StakingToken and StakingApp with constructor parameters.
// 2. Owner funds StakingApp with ETH (receive is restricted to owner).
// 3. User approves and deposits the fixed staking amount.
// 4. After stakingPeriod elapses, user calls claimRewards().Example test flow with Foundry cheatcodes:
stakingToken.mint(tokenAmount);
IERC20(stakingToken).approve(address(stakingApp), tokenAmount);
stakingApp.depositTokens(tokenAmount);
vm.warp(block.timestamp + stakingPeriod);
stakingApp.claimRewards();forge testExpected output includes a summary table:
โญ------------------+--------+--------+---------โฎ
| Test Suite | Passed | Failed | Skipped |
+==============================================+
| StakingAppTest | 14 | 0 | 0 |
|------------------+--------+--------+---------|
| StakingTokenTest | 1 | 0 | 0 |
โฐ------------------+--------+--------+---------โฏ
Show logs and traces for each test:
forge test -vvvMaximum verbosity (stack traces on failure):
forge test -vvvvforge test --match-path test/StakingAppTest.t.solforge test --match-test testDepositTokensCorrectlyGenerate a gas usage report for all tests:
forge test --gas-reportforge coverage| Suite | Tests | Scope |
|---|---|---|
StakingAppTest |
14 | Deployment, deposits, withdrawals, rewards, owner access, ETH funding |
StakingTokenTest |
1 | Token minting |
StakingApp consists of two main contracts and three actor roles:
flowchart TB
subgraph Contracts
ST["๐ช StakingToken<br/>(ERC-20)"]
SA["๐ฆ StakingApp<br/>(Ownable)"]
end
subgraph Actors
Owner["๐ค Owner"]
User["๐ค Staker"]
end
Owner -->|"fund ETH (receive)"| SA
Owner -->|"changeStakingPeriod()"| SA
User -->|"mint()"| ST
User -->|"approve + depositTokens()"| SA
SA -->|"safeTransferFrom"| ST
User -->|"withdrawTokens()"| SA
SA -->|"safeTransfer"| ST
User -->|"claimRewards()"| SA
SA -->|"ETH transfer"| User
| Contract | Responsibility |
|---|---|
StakingToken |
Simple mintable ERC-20 token used as the staking asset |
StakingApp |
Holds staked tokens, tracks user balances and lock timestamps, distributes ETH rewards |
| Variable | Description |
|---|---|
stakingToken |
Address of the ERC-20 token to stake |
stakingPeriod |
Minimum time (seconds) before rewards can be claimed |
fixedStakingAmount |
Exact token amount required per deposit |
rewardPerPeriod |
ETH reward paid per completed staking period |
userBalance |
Active stake amount per user |
elapsePeriod |
Timestamp when the user started or last claimed |
- Deposit โ User approves and calls
depositTokens(fixedStakingAmount). Only one active stake per address. - Wait โ Staking period must elapse. Multiple periods accrue proportionally.
- Claim โ User calls
claimRewards()to receive ETH. The elapsed timer advances by claimed periods. - Withdraw โ User calls
withdrawTokens()to recover staked ERC-20 tokens (CEI pattern applied).
- Fund โ Owner sends ETH to the contract via
receive()(owner-only). - Configure โ Owner calls
changeStakingPeriod()to update the lock duration.
โ ๏ธ This project is intended for learning and demonstration purposes only. It has not undergone a professional security audit.
| Area | Detail |
|---|---|
| ๐ Educational scope | Not production-ready; use at your own risk |
| ๐ธ Owner trust | Owner controls ETH funding and staking period โ users must trust the owner |
| ๐ Reentrancy | Withdrawals follow CEI; reward claims use low-level call โ review before mainnet use |
| ๐ช Token model | StakingToken.mint() is unrestricted โ suitable for testing only |
| ๐ฆ Dependencies | Keep OpenZeppelin and forge-std on tagged releases aligned with foundry.lock |
- Review staking, withdrawal, and reward logic in
src/StakingApp.sol - Run the full test suite:
forge test - Consider a professional audit
- Restrict token minting and add access controls as needed
- Keep dependencies pinned to tagged releases
If you discover a security issue, please do not open a public GitHub issue. Contact the repository owner directly (see About the Author).
Smart contracts carry inherent technical and financial risk. Use this repository at your own responsibility.
This project does not include a script/ deployment folder yet. All operations are performed through Foundry CLI commands:
| Command | Description |
|---|---|
forge build |
Compile all contracts |
forge test |
Run the full test suite |
forge test -vvv |
Run tests with detailed traces |
forge test --gas-report |
Show gas usage per function |
forge coverage |
Generate test coverage report |
forge fmt |
Format Solidity source files |
forge fmt --check |
Verify formatting (CI-friendly) |
forge clean |
Remove cache/ and out/ artifacts |
anvil |
Start a local Ethereum node for manual testing |
cast call <addr> <sig> |
Read on-chain state from a deployed contract |
cast send <addr> <sig> |
Send a transaction to a deployed contract |
To add deployment automation, create a script/ directory:
mkdir scriptThen add a Foundry script (e.g. script/Deploy.s.sol) and run it with:
forge script script/Deploy.s.sol --rpc-url <RPC_URL> --broadcastStakingApp is released under the MIT License.
SPDX identifiers in source files: // SPDX-License-Identifier: MIT
| Name | Noelia Luz Fernรกndez |
| GitHub | @Noelialuz |
| https://www.linkedin.com/in/noelia-luz-fernandez-03404440/ | |
| noelia_luz_fernandez@hotmail.com |
- Foundry Book โ compilation, testing, and cheatcodes
- OpenZeppelin Contracts documentation โ ERC-20, access control, and
SafeERC20 - OpenZeppelin Contracts repository โ dependency source and release tags