DogWithCapStaking Contract
Detailed technical documentation for the main staking contract.
Contract Information
Contract: DogWithCapStaking
Version: V8 (Simplified Final)
License: MIT
Solidity: ^0.8.19Deployed Addresses:
Network: WicChain Testnet
Chain ID: 6689
Address: 0x0EdA695A21E38C1953B80578fd84Ea4923Fe5D6cArchitecture
Inheritance
DogWithCapStaking is ReentrancyGuard, Pausable, OwnableInherited Contracts:
ReentrancyGuard- Protection against reentrancy attacksPausable- Emergency pause functionalityOwnable- Access control for admin functions
Dependencies
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/security/Pausable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";Core Data Structures
StakePosition
struct StakePosition {
uint256 amount; // Fixed stake amount
uint256 claimedInterest; // Total interest claimed
uint64 startTime; // Position creation time
uint64 lastClaimTime; // Last claim timestamp
bool isActive; // Always true (permanent)
}Key Points:
- Amount never changes (permanent staking)
- Interest accumulates over time
- Each position has unique ID
- Multiple positions per user allowed
UserInfo
struct UserInfo {
uint256 totalStaked; // Sum of all positions
uint256 unclaimedRewards; // Referral rewards
uint64 firstStakeTime; // First stake timestamp
address referrer; // Who referred user
MilestoneLevel milestone; // Current level
bool hasEverStaked; // Has user staked
}ContractStats
struct ContractStats {
uint256 totalStaked; // Total across all users
uint256 totalUsers; // Unique user count
uint256 rewardPool; // Available rewards
uint256 totalPositions; // Total positions created
uint256 totalInterestPaid; // Interest distributed
uint256 totalReferralsPaid; // Referral rewards paid
}MilestoneVesting
struct MilestoneVesting {
uint256 totalAmount; // Total bonus amount
uint256 claimedAmount; // Amount already claimed
uint64 vestingStart; // Start timestamp
uint64 vestingEnd; // End timestamp
uint64 lastClaimTime; // Last claim time
bool isActive; // Vesting active
}Constants
// Rate precision
uint256 public constant RATE_PRECISION = 100000;
// Referral rate (5%)
uint256 public constant REFERRAL_RATE = 5000;
// Minimum stake amount (100 WICC)
uint256 public constant MIN_STAKE_AMOUNT = 100 * 10**18;
// Cooldown between actions (60 seconds)
uint256 public constant CLAIM_COOLDOWN = 60;
// Interest rate bounds
uint256 public constant MIN_DAILY_INTEREST_RATE = 50; // 0.05%
uint256 public constant MAX_DAILY_INTEREST_RATE = 1000; // 1%State Variables
Token Configuration
IERC20 public immutable stakingToken;Dynamic Interest Rate
uint256 public dailyInterestRate = 200; // 0.2% default
InterestRateChange[] public interestRateHistory;Cash Out System
uint256 public nextCashOutRequestId = 1;
uint256 public minCashOutAmount = 10 * 10**18;
uint256 public maxCashOutAmount = 1000000 * 10**18;
uint256 public cashOutFeeRate = 100; // 0.1%
bool public cashOutEnabled = true;
uint256 public totalCashOutTokensInContract = 0;Storage Mappings
// User data
mapping(address => UserInfo) public users;
mapping(address => UserReferralData) private userReferralData;
mapping(address => mapping(uint256 => StakePosition)) public userPositions;
// Position tracking
mapping(address => uint256[]) public userPositionIds;
mapping(uint256 => address) public positionOwner;
// Milestone vesting
mapping(address => mapping(uint8 => MilestoneVesting)) public milestoneVesting;
mapping(address => mapping(uint8 => bool)) public milestoneAchieved;
// Rate limiting
mapping(address => uint256) public lastActionTime;
// Referrals
mapping(address => address[]) public userReferees;Core Functions
stake()
function stake(
uint256 amount,
address referrer
) external nonReentrant whenNotPaused rateLimited
returns (uint256 positionId)Description: Creates a new permanent staking position.
Parameters:
amount- Amount of WICC tokens to stakereferrer- Optional referrer address (0x0 if none)
Returns:
positionId- Unique ID of created position
Requirements:
- Amount >= 100 WICC
- User must approve tokens first
- Cannot refer yourself
- Must wait for cooldown
Emits:
PositionCreated(user, positionId, amount, referrer, timestamp)ReferralReward(referrer, referee, stakeAmount, rewardAmount, ...)UserStatsUpdate(user, totalStaked, totalPositions, milestone, ...)
claimPositionInterest()
function claimPositionInterest(uint256 positionId)
external
nonReentrant
whenNotPaused
rateLimited
validPosition(positionId)
returns (uint256 claimedAmount)Description: Claims accumulated interest from a specific position.
Parameters:
positionId- ID of position to claim from
Returns:
claimedAmount- Amount of interest claimed
Requirements:
- Position must exist and be active
- At least 24 hours since last claim
- Sufficient reward pool
- Cooldown not active
Emits:
PositionClaimed(user, positionId, interestClaimed, totalClaimed, paidDays, timestamp)
claimAllPositions()
function claimAllPositions()
external
nonReentrant
whenNotPaused
rateLimitedDescription: Claims interest from all active positions in one transaction.
Gas Optimization: More efficient than claiming individually.
Emits:
PositionClaimedfor each position with claimable interest
claimReferralRewards()
function claimReferralRewards()
external
nonReentrant
whenNotPaused
rateLimitedDescription: Claims all accumulated referral commission rewards.
Requirements:
- Must have unclaimed rewards > 0
- Sufficient reward pool
- Cooldown not active
Emits:
ReferralRewardsClaimed(user, claimedAmount, remainingRewards, timestamp)
claimMilestoneVesting()
function claimMilestoneVesting(uint8[] calldata milestoneLevels)
external
nonReentrant
whenNotPaused
rateLimitedDescription: Claims vested amounts from milestone bonuses.
Parameters:
milestoneLevels- Array of milestone levels to claim (1-4)
Vesting Type: Linear vesting with no cliff
Emits:
MilestoneVestingClaimed(user, level, claimedAmount, remainingAmount, timestamp)
View Functions
getUserInfo()
function getUserInfo(address userAddr)
external
view
returns (
uint256 totalStaked,
uint256 unclaimedReferralRewards,
uint256 totalPendingInterest,
uint256 totalVestingAmount,
uint256 totalClaimableVesting,
address referrer,
MilestoneLevel milestone,
uint256 totalPositions
)Returns complete user state including:
- Total staked across all positions
- Pending rewards (referral + interest + vesting)
- Referrer information
- Current milestone level
- Position count
getPosition()
function getPosition(address user, uint256 positionId)
external
view
returns (
uint256 amount,
uint256 claimedInterest,
uint256 pendingInterest,
uint256 startTime,
uint256 lastClaimTime,
uint256 stakingDays,
bool isActive
)Returns position details including:
- Staked amount (unchanging)
- Interest claimed so far
- Currently pending interest
- Time information
- Activity status
getContractStats()
function getContractStats()
external
view
returns (
uint256 totalStaked,
uint256 totalUsers,
uint256 rewardPool,
uint256 totalPositions,
uint256 contractBalance,
uint256 totalInterestPaid,
uint256 totalReferralsPaid,
uint256 pendingCashOutAmount,
uint256 currentDailyRate
)Returns platform-wide statistics.
Admin Functions
These functions can only be called by contract owner.
setDailyInterestRate()
function setDailyInterestRate(uint256 newRate, string calldata reason)
external
onlyOwnerDescription: Updates the daily interest rate for all positions.
Parameters:
newRate- New rate in basis points (200 = 0.2%)reason- Explanation for rate change
Constraints:
- Rate must be between 0.05% and 1%
- Change is recorded in history
- Affects all existing and future positions
Emits:
DailyInterestRateChanged(oldRate, newRate, changedBy, reason, timestamp)
depositRewards()
function depositRewards(uint256 amount)
external
onlyOwnerDescription: Adds tokens to the reward pool.
Use Case: Ensure platform can pay interest and rewards.
pause() / unpause()
function pause() external onlyOwner
function unpause() external onlyOwnerDescription: Emergency pause/resume all user operations.
Pauses: All staking, claiming, and reward functions.
Security Features
Reentrancy Protection
modifier nonReentrant() All state-changing functions use ReentrancyGuard.
Rate Limiting
modifier rateLimited() {
require(
block.timestamp >= lastActionTime[msg.sender] + CLAIM_COOLDOWN,
"Cooldown active"
);
_;
lastActionTime[msg.sender] = block.timestamp;
}Prevents:
- Spam transactions
- Manipulation attempts
- Network congestion
Access Control
modifier onlyOwner()Admin functions restricted to contract owner.
Pausable
Emergency pause mechanism for critical situations.
Gas Optimization
Tips for Users
- Batch Claims: Use
claimAllPositions()instead of individual claims - Timing: Claim during low network activity
- Approval: Approve unlimited to avoid repeated approvals
- Cooldown: Wait for full cooldown before next action
Expected Gas Costs
| Function | Estimated Gas | Cost (estimate) |
|---|---|---|
| approve | ~50,000 | 0.20 |
| stake | ~150,000 | 0.60 |
| claimPositionInterest | ~100,000 | 0.40 |
| claimAllPositions | ~200,000 | 0.80 |
| claimReferralRewards | ~80,000 | 0.30 |
| claimMilestoneVesting | ~120,000 | 0.50 |
Actual costs depend on network congestion and gas prices.
Testing
Example Test Cases
describe("DogWithCapStaking", () => {
it("Should stake tokens successfully", async () => {
await token.approve(staking.address, amount);
await staking.stake(amount, referrer);
// Assert position created
});
it("Should enforce minimum stake", async () => {
await expect(
staking.stake(99, ADDRESS_ZERO)
).to.be.revertedWith("AmountTooLow");
});
it("Should prevent self-referral", async () => {
await expect(
staking.stake(amount, user.address)
).to.be.revertedWith("SelfReferral");
});
it("Should calculate interest correctly", async () => {
// Stake
// Wait 1 day
// Claim
// Assert correct amount
});
});Upgrade Path
This contract is NOT upgradeable. All functionality is fixed at deployment.
If upgrades needed:
- Deploy new contract
- Migrate users manually
- Or implement proxy pattern in V9