Shared Libraries
Core libraries providing foundational functionality, data structures, and utilities used across all TokenOps vesting contracts. These libraries ensure consistency, efficiency, and maintainability throughout the system.
📚 Available Libraries
Core Computational Libraries
library TokenVestingLib {
struct Vesting { /* ... */ }
function calculateVestedAmount(Vesting memory, uint32) pure returns (uint256);
}
Central library containing vesting calculations and data structures. Provides gas-optimized algorithms for all vesting computations across the system.
Access Control Libraries
abstract contract AccessProtected {
enum AdminRole { None, Standard, Super }
modifier onlyAdmin() { /* ... */ }
}
Multi-admin access control system with role-based permissions and emergency controls for secure administrative operations.
Error Management Libraries
library Errors {
error InvalidRecipient();
error VestingNotFound();
error InsufficientFunding();
}
Centralized error definitions providing standardized, gas-efficient error handling across all contracts.
Fee Management Libraries
abstract contract FactoryFeeManager {
enum FeeType { Gas, DistributionToken }
function getFeeConfig(address) returns (FeeType, uint256);
}
Sophisticated fee management for factory contracts with support for custom fee structures and flexible collection mechanisms.
🎯 Library Overview
TokenVestingLib - Core Computations
Purpose: Mathematical foundation for all vesting operations Key Features:
- Gas-optimized vesting calculations
- Packed struct design for storage efficiency
- Support for complex vesting schedules (cliff, linear, initial unlock)
- Consistent calculation logic across all manager types
Usage Example:
using TokenVestingLib for TokenVestingLib.Vesting;
function getVestedAmount(bytes32 vestingId) public view returns (uint256) {
return vestings[vestingId].calculateVestedAmount(uint32(block.timestamp));
}
AccessProtected - Security Framework
Purpose: Role-based access control and security management Key Features:
- Multi-admin support with hierarchical roles
- Emergency pause/unpause capabilities
- Secure admin role management
- Event-driven transparency
Usage Example:
contract MyVestingManager is AccessProtected {
constructor() AccessProtected(msg.sender) {}
function createVesting() external onlyAdmin whenNotPaused {
// Admin-only function with pause protection
}
}
Errors - Standardized Error Handling
Purpose: Centralized, gas-efficient error management Key Features:
- Custom errors instead of string-based reverts
- Categorized by functional domain
- Parameterized errors for better debugging
- Significant gas savings on deployment and execution
Usage Example:
function claimVesting(bytes32 vestingId) external {
if (vestings[vestingId].recipient != msg.sender) {
revert Errors.NotAuthorized();
}
if (claimableAmount == 0) {
revert Errors.NoTokensAvailable();
}
}
FactoryFeeManager - Fee Collection System
Purpose: Flexible fee management for factory-deployed contracts Key Features:
- Dual fee types (gas-based and token-based)
- Custom fee configurations per deployer
- Automatic fee collection and distribution
- Volume-based discounts and tier systems
Usage Example:
contract VestingFactory is FactoryFeeManager {
function deployManager(address token) external payable {
collectGasFee(msg.sender);
// Deploy with fee configuration
}
}
🔧 Integration Patterns
Combined Usage
Most vesting contracts use multiple libraries together:
contract TokenVestingManager is AccessProtected {
using TokenVestingLib for TokenVestingLib.Vesting;
mapping(bytes32 => TokenVestingLib.Vesting) public vestings;
function createVesting(
address recipient,
uint256 amount,
uint32 duration
) external onlyAdmin returns (bytes32) {
if (recipient == address(0)) {
revert Errors.InvalidRecipient();
}
bytes32 vestingId = keccak256(abi.encodePacked(recipient, amount, block.timestamp));
vestings[vestingId] = TokenVestingLib.Vesting({
recipient: recipient,
startTimestamp: uint32(block.timestamp),
endTimestamp: uint32(block.timestamp + duration),
// ... other fields
});
return vestingId;
}
function claim(bytes32 vestingId) external {
TokenVestingLib.Vesting storage vesting = vestings[vestingId];
if (vesting.recipient != msg.sender) {
revert Errors.NotAuthorized();
}
uint256 vested = vesting.calculateVestedAmount(uint32(block.timestamp));
uint256 claimable = vested - vesting.claimedAmount;
if (claimable == 0) {
revert Errors.NoTokensAvailable();
}
vesting.claimedAmount += claimable;
// Transfer tokens...
}
}
Factory Integration Pattern
contract VestingManagerFactory is AccessProtected, FactoryFeeManager {
constructor(
address feeCollector,
uint256 defaultGasFee,
uint256 defaultTokenFee
)
AccessProtected(msg.sender)
FactoryFeeManager(feeCollector, defaultGasFee, defaultTokenFee)
{}
function deployVestingManager(
address token
) external payable onlyAdmin returns (address) {
// Collect deployment fee
collectGasFee(msg.sender);
// Get fee configuration for the deployed manager
(ITypes.FeeType feeType, uint256 fee) = getFeeConfig(msg.sender);
// Deploy with proper configuration
TokenVestingManager manager = new TokenVestingManager(
token,
feeType,
fee,
feeCollector
);
// Transfer admin privileges
manager.transferAdmin(msg.sender);
return address(manager);
}
}
📊 Library Comparison
Library | Purpose | Gas Impact | Complexity | Dependencies |
---|---|---|---|---|
TokenVestingLib | Core calculations | High savings | Medium | None |
AccessProtected | Access control | Medium cost | Low | None |
Errors | Error handling | High savings | Low | None |
FactoryFeeManager | Fee management | Medium cost | High | AccessProtected |
🚀 Quick Start
Basic Manager Implementation
pragma solidity ^0.8.19;
import {AccessProtected} from "./libraries/AccessProtected.sol";
import {TokenVestingLib} from "./libraries/TokenVestingLib.sol";
import {Errors} from "./libraries/Errors.sol";
contract BasicVestingManager is AccessProtected {
using TokenVestingLib for TokenVestingLib.Vesting;
mapping(bytes32 => TokenVestingLib.Vesting) public vestings;
constructor(address initialAdmin) AccessProtected(initialAdmin) {}
function createVesting(
TokenVestingLib.VestingParams memory params
) external onlyAdmin returns (bytes32) {
if (params._recipient == address(0)) {
revert Errors.InvalidRecipient();
}
vestings[params._vestingId] = TokenVestingLib.Vesting({
recipient: params._recipient,
startTimestamp: params._startTimestamp,
endTimestamp: params._endTimestamp,
// ... other fields from params
});
return params._vestingId;
}
function getVestedAmount(bytes32 vestingId) external view returns (uint256) {
return vestings[vestingId].calculateVestedAmount(uint32(block.timestamp));
}
}
Factory Implementation
contract VestingFactory is AccessProtected, FactoryFeeManager {
constructor()
AccessProtected(msg.sender)
FactoryFeeManager(
msg.sender, // Fee collector
0.001 ether, // Default gas fee
100 // Default token fee (1%)
)
{}
function deployBasicManager(
address token
) external payable returns (BasicVestingManager) {
collectGasFee(msg.sender);
BasicVestingManager manager = new BasicVestingManager(msg.sender);
emit ManagerDeployed(address(manager), msg.sender);
return manager;
}
event ManagerDeployed(address indexed manager, address indexed deployer);
}
🛡️ Security Considerations
Library Security
TokenVestingLib
- Pure functions - No state modifications, safe for all contexts
- Input validation - Calling contracts must validate inputs
- Overflow protection - Uses Solidity 0.8+ built-in protection
- Precision handling - Conservative calculations prevent over-vesting
AccessProtected
- Admin lockout prevention - Cannot remove last admin
- Role validation - Proper role checking in all functions
- Emergency controls - Pause mechanisms for security incidents
- Event logging - All admin changes logged for transparency
Errors
- Information leakage - No sensitive data in error messages
- Gas efficiency - Custom errors reduce deployment and runtime costs
- Consistency - Standardized error types across system
FactoryFeeManager
- Fee validation - Maximum fee limits prevent abuse
- Access control - Admin-only fee configuration
- Payment verification - Proper fee collection before services
- Refund mechanisms - Excess payments automatically refunded
Integration Security
Common Patterns
// Good: Proper input validation
function createVesting(address recipient, uint256 amount) external onlyAdmin {
if (recipient == address(0)) revert Errors.InvalidRecipient();
if (amount == 0) revert Errors.ZeroAmount();
// Proceed with creation
}
// Bad: Missing validation
function createVesting(address recipient, uint256 amount) external onlyAdmin {
// Direct creation without validation
}
Error Handling
// Good: Specific errors with context
if (amount > maxAllowed) {
revert Errors.AmountExceedsMaximum(amount, maxAllowed);
}
// Bad: Generic errors
if (amount > maxAllowed) {
revert Errors.InvalidInput();
}
📈 Performance Optimization
Gas Optimization Strategies
Storage Layout
// Optimized: Pack related fields together
struct Vesting {
address recipient; // 20 bytes
uint32 startTimestamp; // 4 bytes | Same slot
uint32 endTimestamp; // 4 bytes | Same slot
uint32 cliffTimestamp; // 4 bytes | Same slot
uint256 amount; // 32 bytes | New slot
}
Calculation Efficiency
// Optimized: Single calculation with memory parameter
uint256 vested = vesting.calculateVestedAmount(uint32(block.timestamp));
// Less optimal: Multiple storage reads
uint256 vested = calculateVested(vestingId, block.timestamp);
Error Handling
// Optimized: Custom errors
if (invalid) revert Errors.InvalidInput();
// Less optimal: String errors
require(!invalid, "Invalid input parameter");
🔄 Upgrade Considerations
Library Versioning
Libraries should be versioned for compatibility:
// contracts/libraries/v1/TokenVestingLib.sol
library TokenVestingLibV1 {
// Version 1 implementation
}
// contracts/libraries/v2/TokenVestingLib.sol
library TokenVestingLibV2 {
// Version 2 with new features
}
Migration Strategies
Data Structure Evolution
// V1 Vesting struct
struct VestingV1 {
address recipient;
uint256 amount;
uint32 startTime;
}
// V2 Vesting struct with additional fields
struct VestingV2 {
address recipient;
uint256 amount;
uint32 startTime;
uint32 cliffTime; // New field
bool isRevocable; // New field
}
// Migration function
function migrateVesting(bytes32 vestingId) external {
VestingV1 memory oldVesting = oldVestings[vestingId];
newVestings[vestingId] = VestingV2({
recipient: oldVesting.recipient,
amount: oldVesting.amount,
startTime: oldVesting.startTime,
cliffTime: 0, // Default value
isRevocable: true // Default value
});
}
📋 Best Practices
Development Guidelines
- Import specific items instead of entire libraries
- Use library functions consistently across similar operations
- Validate inputs before calling library functions
- Handle errors appropriately with specific error types
- Test edge cases thoroughly, especially for calculations
- Document library usage in your contracts
- Monitor gas costs when using complex library functions
Code Organization
// Good: Clear imports and usage
import {TokenVestingLib} from "./libraries/TokenVestingLib.sol";
import {Errors} from "./libraries/Errors.sol";
contract VestingManager {
using TokenVestingLib for TokenVestingLib.Vesting;
function processVesting(bytes32 id) external {
if (vestings[id].recipient == address(0)) {
revert Errors.InvalidVestingId();
}
uint256 amount = vestings[id].calculateVestedAmount(uint32(block.timestamp));
// Process amount...
}
}
Testing Approach
contract LibraryTest {
function testVestingCalculation() public {
TokenVestingLib.Vesting memory vesting = createTestVesting();
uint256 vested = vesting.calculateVestedAmount(uint32(block.timestamp));
assertEq(vested, expectedAmount);
}
function testErrorHandling() public {
vm.expectRevert(Errors.InvalidRecipient.selector);
manager.createVesting(address(0), 1000);
}
}
The shared libraries form the backbone of the TokenOps vesting system, providing consistent, efficient, and secure functionality across all contracts while maintaining flexibility for different use cases and deployment patterns.