Multi-Type Token Functionality
The RestrictedLockupToken
includes built-in support for multiple token types to address different regulatory requirements. This allows a single token contract to handle various investor classifications such as RegS (Regulation S), RegD (Regulation D), RegCF (Regulation CF), and institutional investors.
Key Features
Enhanced Token Tracking
- Multiple Token Types: Single contract supports different regulatory classifications
- Timestamp Preservation: Each token holding maintains its original mint timestamp
- FIFO Transfer Logic: Oldest holdings are transferred first, preserving regulatory compliance
- Type-Specific Restrictions: Different rules for different token types and recipients
Token Types
Type | Description | Typical Use Case |
---|---|---|
0 | Default/Unclassified | Fallback for unmatched identities |
1 | RegS (Regulation S) | Foreign investors |
2 | RegD (Regulation D) | US accredited investors |
3 | RegCF (Regulation CF) | US retail investors |
4+ | Custom | Jurisdiction-specific rules |
Data Structure
struct TokenHolding {
uint256 amount; // Token amount
uint256 tokenType; // Type (RegS, RegD, etc.)
uint256 mintTimestamp; // When minted
}
Optimized Holdings Storage with Bitmask Approach
The token contract uses an advanced bitmask-based storage optimization to efficiently track token holdings across different types and timestamps. This approach provides significant gas savings for users with multiple holdings while maintaining fast query performance.
Why Bitmask Optimization?
Traditional approaches that store holdings in arrays become expensive as the number of holdings grows. The bitmask approach provides:
- Constant-time existence checks: Using bit operations to verify holding existence
- Efficient iteration: Direct bitmap scanning without array enumeration
- Optimal storage: Compact representation of holding indexes using bit positions
- Batch operations: Multiple holdings can be processed in single storage operations
Storage Structure
// Global registry of all mint timestamps for each token type
mapping(uint256 => uint256[]) globalMintTimestamps;
// Actual token balances by type and timestamp
mapping(address => mapping(uint256 => mapping(uint256 => uint256))) balancesByTypeAndTime;
// Bitmask indicating which global timestamps a wallet has holdings for
mapping(address => mapping(uint256 => uint256[])) walletIndexesByType;
Bitmask Operations with Popcount and BitScan
The system leverages two key bit manipulation libraries:
Popcount (Population Count)
- Purpose: Counts the number of set bits (1s) in a bitmask
- Usage: Determines total number of holdings a wallet has for a token type
- Efficiency: Single CPU instruction on modern processors
- Example:
bitmask.popcount256B()
returns count of holdings
function holdingCountOf(address who_, uint256 tokenType_) external view returns (uint256) {
uint256 count = 0;
for (uint256 i = 0; i < walletIndexesByType[who_][tokenType_].length; i++) {
count += walletIndexesByType[who_][tokenType_][i].popcount256B();
}
return count;
}
BitScan (Bit Scanning)
- Purpose: Finds the position of the first set bit in a bitmask
- Usage: Locates specific holdings during iteration without array traversal
- Types:
bitScanForward256()
finds lowest set bit position - Efficiency: Enables direct access to holding positions
function _searchWordForHolding(...) internal view returns (bool, uint256, uint256) {
while (tempWord != 0) {
uint256 globalIndex = wordIndex_ * 256 + tempWord.bitScanForward256();
// Process holding at this index
tempWord &= tempWord - 1; // Clear the lowest set bit
}
}
Gas Optimization Benefits
- Batch Bitmap Updates: Multiple holdings can be set/cleared in single operations
- Direct Index Access: No need to iterate through arrays to find holdings
- Efficient Counting: Popcount provides instant holding counts
- Minimal Storage Overhead: Bitmasks are much more compact than holding arrays
Two-Layer Rule System
The system implements a sophisticated two-layer approach through the TransferRules
contract:
- Token Type Rules: Map investor identity → token type assignment
- Transfer Rules: Define transfer restrictions based on token type + recipient identity
- Default Token Type Rule: Fallback token type when no region/accreditation rule matches
For detailed information about transfer rules and configuration, see Transfer Restrictions.
Enhanced Minting with Token Types
The token contract provides flexible minting capabilities that support both automatic token type determination and explicit type specification.
Automatic Token Type Minting
When using the standard mint()
function, the token type is automatically determined based on the recipient's identity and current rules.
token.mint(recipient, amount);
Explicit Token Type Minting
For precise control, use mintTokenType()
to specify the exact token type:
// Explicitly mint RegD tokens (type 2)
token.mintTokenType(recipient, amount, 2);
Multi-Type Functions
Minting
// Automatic type determination based on investor identity
token.mint(investor, 1000 * 10**18);
// Manual type specification
token.mintTokenType(investor, 500 * 10**18, 2); // RegD tokens
Querying
// Number of separate holdings
uint256 holdingCount = token.holdingCountOf(investor, tokenType);
// Get specific holding details
(uint256 balance, uint256 mintTimestamp) = token.holdingOf(investor, tokenType, index);
Transfer Validation
// Check if transfer is allowed for specific token type
uint8 restrictionCode = token.detectTransferRestrictionFor(
tokenType,
mintTimestamp,
recipient
);
Benefits
- Regulatory Compliance: Supports multiple securities regulations in one contract
- Gas Efficiency: Optimized storage and transfer operations
- Audit Trail: Complete history with timestamps for regulatory reporting
- Flexibility: Configurable rules without contract upgrades
- Backward Compatibility: All existing ERC20 functionality preserved
For more detailed information, see: