Skip to main content

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

TypeDescriptionTypical Use Case
0Default/UnclassifiedFallback for unmatched identities
1RegS (Regulation S)Foreign investors
2RegD (Regulation D)US accredited investors
3RegCF (Regulation CF)US retail investors
4+CustomJurisdiction-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

  1. Batch Bitmap Updates: Multiple holdings can be set/cleared in single operations
  2. Direct Index Access: No need to iterate through arrays to find holdings
  3. Efficient Counting: Popcount provides instant holding counts
  4. 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:

  1. Token Type Rules: Map investor identity → token type assignment
  2. Transfer Rules: Define transfer restrictions based on token type + recipient identity
  3. 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: