Building a Finite-Reward DeFi Staking Protocol With Solidity and Foundry
I built StakeVault, a DeFi staking DApp using Solidity, Foundry, React and Ethers.js, with finite rewards and multi-user accounting on Sepolia.
DeFi staking protocols may seem straightforward, but accurately managing the accounting for multiple users, withdrawals, reward claims, and a finite reward pool can be quite complex. The author developed StakeVault to explore these challenges using Solidity smart contracts, Foundry testing, and a React frontend on the Ethereum Sepolia testnet.
At the core of StakeVault is the need to accurately track each user's deposits, the total amount staked by the protocol, and the duration of each staking position. The protocol must also determine how rewards are distributed among users and handle the scenario when the reward pool runs out. To address these issues, StakeVault separates user accounting from global accounting.
When a user deposits tokens, the global totalStaked value increases, while the individual user's staked amount remains unchanged. This distinction is crucial for the frontend, which should display the total staked amount for the entire protocol, not just the amount tied to a specific wallet.
StakeVault employs cumulative reward-per-token accounting to manage rewards. The rewardPerToken value is updated based on the new rewards earned and the total staked amount. Users can then calculate their earned rewards based on their stake and the change in cumulative rewards per token. This approach allows the protocol to handle multiple users without the need for individual reward calculations on every block.
A key design decision in StakeVault is that rewards are finite. Instead of simply calculating rewards as elapsedTime × rewardRate, the protocol introduces a rewardRemaining variable that tracks the remaining tokens in the pool. The lifecycle of the reward system involves funding the pool, emitting rewards, accruing and claiming rewards by users, and ultimately stopping emissions once the pool is exhausted. Additional REWARD tokens can be funded into the vault when necessary.
The smart contract architecture of StakeVault consists of three primary contracts: StakeToken (ERC-20 token deposited by users), RewardToken (ERC-20 token distributed as staking rewards), and StakingVault (the core protocol contract). The StakingVault manages user staking positions, total protocol stake, reward calculations, distribution, withdrawals, claims, and funding. It also includes emergency pause functionality.
The frontend of StakeVault is built with React, Vite, Ethers.js, and Wagmi, providing wallet connection and exposing key protocol state. Users can view the total staked amount, reward rate, remaining rewards, and their own staked amount, available STAKE, and accrued rewards. This clear distinction between protocol-level and wallet-level state is visually represented in the UI.
Testing of the smart contracts was conducted using Foundry, with an extensive test suite covering various scenarios such as ERC-20 behavior, staking and withdrawal operations, reward accrual, proportional distribution, multiple-user calculations, reward claiming, exit functionality, reward funding, reward rate updates, pause functionality, and reward pool exhaustion. The tests can be executed using the "forge test" command.
StakeVault was deployed on the Ethereum Sepolia testnet, with the contract addresses provided. The live application and source code are available at the provided links. This project demonstrates the integration of ERC-20 tokens, smart contract accounting, reward distribution, Foundry testing, wallet integration, React frontend, and Ethereum Sepolia.
It also emphasizes the importance of distinguishing global state from user-specific state in DeFi development, particularly in multi-wallet interactions with the same staking pool.
Written by urgent.news from HackerNoon's reporting — not their text. Machine-written — may contain errors; check the original before relying on it.