This repository provides comprehensive documentation and example smart contracts for TokenLocker - a universal token locking platform built with security, gas efficiency, and flexibility in mind. The platform supports both ERC20 tokens and native tokens (ETH) for secure time-based token locking.
contracts/
└── TokenLocker.sol # Core token locking contract with activity key management
test/
├── TokenLockerTest.t.sol # Comprehensive unit and integration tests
└── mock/
└── MockERC20.sol # Mock ERC20 token for testing
A universal token locking contract that supports both ERC20 tokens and native tokens (ETH) with activity key-based organization.
Key Features:
- Universal Token Support: Locks both ERC20 tokens and native tokens (ETH, BNB, MATIC, etc.)
- Activity Key Management: Organize and manage locks with arbitrary bytes32 keys
- Flexible Duration: Each lock can be set from 1 second to 1 year (365 days)
- Security: Uses ReentrancyGuard and follows CEI (Checks-Effects-Interactions) pattern
- Gas Efficient: Each lock record is packed into a single storage slot to minimize gas usage
- Permissionless: No admin controls, fully decentralized operation
Core Functions:
lock(bytes32 key, address token, uint128 amount, uint64 lockTime)- Lock tokens for specified duration- For ERC20 tokens: Requires sufficient balance and approval
- For native tokens: Requires sending ETH with the transaction
unlock(bytes32 key, address token)- Unlock tokens after lock period expiresgetLockInfo(bytes32 key, address user, address token)- Query comprehensive lock information
Security Features:
- Custom errors for gas-efficient error handling
- Input validation for zero addresses, amounts, and invalid parameters
- SafeERC20 for secure token transfers
- Native token validation and transfer protection
- Reentrancy protection using OpenZeppelin's ReentrancyGuard
- Precise timestamp validation for lock expiration
Compatibility Notes:
- Supported: Standard ERC20 tokens with stable transfer semantics
- Not Supported:
- Fee-on-transfer / tax tokens
- Deflationary or auto-burn tokens
- Rebasing tokens
- Tokens with transfer hooks or callbacks (e.g., ERC-777 style)
- Any "modified" ERC20 whose transfer amount or behavior deviates from the standard
Constants:
ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE- Native token identifierMAX_LOCK_DURATION = 365 days- Maximum lock durationZERO_ADDRESS = address(0)- Zero address for invalid tokensZERO_BYTES32 = bytes32(0)- Zero bytes32 for invalid keys
Comprehensive test suite covering all contract functionality for both ERC20 and native tokens.
Test Categories:
Core Functionality Tests:
- Successful token locking for ERC20 tokens
- Successful token locking for native tokens (ETH)
- Token unlocking after lock expiration
- Lock information querying and validation
Input Validation Tests:
- Invalid key handling (zero bytes32)
- Invalid token address handling (zero address)
- Invalid amount handling (zero amount)
- Invalid lock time handling (zero or exceeding maximum)
- Message value validation for native tokens
Security Tests:
- Duplicate lock prevention
- Lock expiration validation
- Non-existent lock handling
- Reentrancy protection verification
Multi-User Tests:
- Multiple users with same activity key
- Multiple activity keys per user
- Concurrent lock management
- User isolation and access control
Native Token Tests:
- ETH locking and unlocking workflows
- Native token amount validation
- Message value mismatch handling
- Native token transfer failure handling
Event Testing:
- TokensLocked event emission
- TokensUnlocked event emission
- Event parameter validation
Edge Cases:
- Lock time boundary testing
- Maximum lock duration validation
- Precise timestamp handling
- Gas optimization verification
- Run tests using
forge testfor comprehensive testing - Use
forge test -vvfor verbose output with detailed logs - Use
forge test -vvvfor very verbose output with stack traces - Explore smart contracts in the
contracts/directory - Review test implementations for usage examples
Security:
- Reentrancy protection using OpenZeppelin's ReentrancyGuard
- Input validation for all parameters
- SafeERC20 for secure token transfers
- Native token handling with proper validation
- Precise timestamp validation for lock expiration
- Custom errors for gas-efficient error handling
Gas Efficiency:
- Packed storage variables (LockInfo struct fits in single storage slot)
- Custom errors instead of revert strings
- Immutable variables for constant values
- Optimized storage layout
- Minimal external calls
Flexibility:
- Universal token support (ERC20 and native tokens)
- Activity key-based organization
- Flexible lock durations (1 second to 1 year)
- Multiple concurrent locks per user
- Permissionless operation
Scalability:
- Efficient storage layout
- Minimal gas costs for operations
- Support for large numbers of concurrent locks
- Activity key-based organization for easy management
The contract uses an optimized storage layout:
mapping(bytes32 => mapping(address => mapping(address => LockInfo))) internal userLocks;Storage Structure:
- First level: Activity key (bytes32) - identifies the specific activity or campaign
- Second level: User address - identifies the user who created the lock
- Third level: Token address - identifies the token being locked (ETH_ADDRESS for native tokens)
- Value: LockInfo struct containing amount, lockTime, and startTime
LockInfo Struct (Packed in Single Storage Slot):
amount(128 bits): Amount of tokens lockedlockTime(64 bits): Lock duration in secondsstartTime(64 bits): Lock start timestamp
TokensLocked Event:
event TokensLocked(
bytes32 indexed key,
address indexed user,
address indexed token,
uint128 amount,
uint64 lockTime
);TokensUnlocked Event:
event TokensUnlocked(
bytes32 indexed key,
address indexed user,
address indexed token,
uint128 amount
);The contract uses custom errors for gas-efficient error handling:
InvalidKey()- Thrown when key is zero bytes32InvalidToken()- Thrown when token address is zeroInvalidAmount()- Thrown when amount is zeroInvalidLockTime()- Thrown when lock time is zero or exceeds maximumLockNotFound()- Thrown when lock doesn't existLockNotExpired()- Thrown when attempting to unlock before expirationAlreadyLocked()- Thrown when lock already exists for key/token combinationNativeTransferFailed()- Thrown when native ETH transfer failsMsgValueMismatch()- Thrown when msg.value doesn't match amount
We welcome contributions! Please follow these steps:
- Fork the repository
- Create a feature branch
- Make your changes
- Add or update tests as needed
- Ensure all tests pass with
forge test - Submit a Pull Request with a clear description of your changes
This project is licensed under the MIT License - see the LICENSE file for details.