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.