A comprehensive Solidity smart contract for token staking with fixed-duration periods, reward distribution, and administrative controls. Built with Hardhat and featuring extensive test coverage.
- 24-Hour Deposit Window: Users can only stake during a 24-hour window after staking is started
- 14-Day Staking Duration: Fixed staking period of 14 days
- 10% Rewards: Guaranteed 10% reward on staked tokens
- 150M Token Cap: Maximum total stake limit across all users
- Incremental Staking: Users can increase their stake multiple times during the deposit window
- Owner Controls: Administrative functions for reward management and token recovery
- Reentrancy Protection: Built-in security against reentrancy attacks
- Comprehensive Testing: 78 test cases covering all scenarios including incremental staking
Parameter | Value |
---|---|
Deposit Window | 24 hours |
Staking Duration | 14 days |
Reward Percentage | 10% |
Maximum Total Stake | 150,000,000 tokens |
Stakes Per User | Multiple (during deposit window) |
Minimum Stake | > 0 tokens |
- Node.js (v16 or higher)
- npm or yarn
- Clone the repository:
git clone <repository-url>
cd uomi-staking
- Install dependencies:
npm install
- Compile contracts:
npx hardhat compile
Run the complete test suite:
npx hardhat test
The project includes 78 comprehensive tests organized in four categories:
- Main Tests (
Staking.test.js
): Core functionality testing including incremental staking - Incremental Staking Tests (
Staking.incremental.test.js
): Dedicated tests for multiple stake functionality - Integration Tests (
Staking.integration.test.js
): End-to-end scenarios - Error Handling Tests (
Staking.errors.test.js
): Edge cases and error conditions
All tests pass successfully, ensuring robust contract behavior.
Initiates the staking period and opens the 24-hour deposit window.
function startStaking() external onlyOwner
Allows users to stake tokens during the deposit window. Users can call this function multiple times to increase their stake.
function stake(uint256 _amount) external
Requirements:
- Staking must be active and within deposit window
- Amount must be greater than 0
- Total stake (including previous stakes) must not exceed 150M cap
- User must have sufficient token balance and allowance
Events:
- First stake: Emits
Staked(user, amount)
- Subsequent stakes: Emits
StakeIncreased(user, additionalAmount, totalAmount)
Allows users to withdraw their staked tokens plus rewards after the staking period ends.
function claim() external
Requirements:
- Staking period must have ended
- User must have tokens staked
- Rewards must not have been claimed already
Deposits tokens to the contract for reward distribution.
function depositRewards(uint256 _amount) external onlyOwner
Withdraws unused tokens from the contract after staking ends.
function withdrawUnusedTokens(uint256 _amount) external onlyOwner
Returns the staked amount and calculated reward for a user.
function calculateReward(address _user) external view returns (uint256 stakedAmount, uint256 rewardAmount)
Checks if the deposit window is currently active.
function isDepositWindowOpen() external view returns (bool)
Checks if the staking period has ended.
function isStakingEnded() external view returns (bool)
Returns the remaining staking capacity before hitting the 150M cap.
function getRemainingCapacity() external view returns (uint256)
Returns comprehensive staking information for a user.
function getUserStakeInfo(address _user) external view returns (uint256 stakedAmount, bool claimed, uint256 potentialReward)
- Reentrancy Guard: Protects against reentrancy attacks
- Access Control: Owner-only functions for administrative tasks
- Input Validation: Comprehensive checks on all parameters
- State Management: Proper state transitions and validations
- Safe Math: Built-in overflow protection (Solidity 0.8.20+)
- Pre-Start: Contract deployed, staking not yet started
- Deposit Window: 24-hour period where users can stake
- Staking Period: 14-day period where tokens are locked
- Claim Period: Users can withdraw staked tokens + rewards
- No Stake: User has not staked any tokens
- Staked: User has staked tokens during deposit window (can increase multiple times)
- Claimed: User has withdrawn their stake and rewards
// Approve tokens for staking
await token.approve(stakingContract.address, amount);
// Initial stake during deposit window
await stakingContract.stake(ethers.parseUnits("1000", 18));
// Increase stake (multiple times allowed during deposit window)
await stakingContract.stake(ethers.parseUnits("500", 18));
await stakingContract.stake(ethers.parseUnits("300", 18));
// Check staking info
const [staked, claimed, reward] = await stakingContract.getUserStakeInfo(userAddress);
// Claim after staking period ends
await stakingContract.claim();
// Start the staking period
await stakingContract.startStaking();
// Deposit rewards for distribution
await token.approve(stakingContract.address, rewardAmount);
await stakingContract.depositRewards(rewardAmount);
// Withdraw unused tokens after staking ends
await stakingContract.withdrawUnusedTokens(amount);
The contract emits the following events:
StakingStarted(uint256 startTime)
: When staking period beginsStaked(address indexed user, uint256 amount)
: When a user stakes tokens for the first timeStakeIncreased(address indexed user, uint256 additionalAmount, uint256 totalAmount)
: When a user increases their existing stakeClaimed(address indexed user, uint256 stakedAmount, uint256 rewardAmount)
: When a user claims rewards
- OpenZeppelin Contracts v5.3.0:
Ownable
: Access control for administrative functionsReentrancyGuard
: Protection against reentrancy attacksIERC20
: Interface for token interactions
contracts/
βββ Staking.sol # Main staking contract
βββ MockERC20.sol # Test token for testing
test/
βββ Staking.test.js # Core functionality tests
βββ Staking.incremental.test.js # Incremental staking tests
βββ Staking.integration.test.js # Integration scenarios
βββ Staking.errors.test.js # Error handling tests
βββ README.md # Test documentation
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Ensure all tests pass
- Submit a pull request
This project is licensed under the MIT License.
For questions, issues, or contributions, please open an issue in the repository.
Built with β€οΈ using Hardhat and OpenZeppelin