Token Partition

Named tranche (bytes32 key) within ERC-1410 tokens separating shares by legal status such as locked, vested, or freely tradable.

Token Partition is a named tranche within an ERC-1410 token contract that groups tokens sharing the same legal status, transfer restrictions, or economic properties. Each partition is identified by a bytes32 key (typically a keccak256 hash of a descriptive name like "LOCKED" or "VESTED") and maintains its own balance mapping, enabling a single security token contract to represent multiple share classes with different rules.

Partitions are the implementation mechanism for partial fungibility. While the concept of partial fungibility describes the architectural pattern, partitions are the concrete data structures that make it work. Understanding partitions is essential for developers implementing ERC-1400 compliant tokens and for auditors analyzing their security properties.

Technical Implementation

The core data structure uses nested mappings:

1// Partition balances: address → partition → balance
2mapping(address => mapping(bytes32 => uint256)) internal _balances;
3
4// Track which partitions each address holds
5mapping(address => bytes32[]) internal _partitionsOf;
6
7// Total supply per partition
8mapping(bytes32 => uint256) internal _totalSupplyByPartition;
9
10// Global list of all partitions
11bytes32[] internal _totalPartitions;

Common partition identifiers follow naming conventions:

1bytes32 constant LOCKED = keccak256("LOCKED");
2bytes32 constant VESTED = keccak256("VESTED");
3bytes32 constant TRADABLE = keccak256("TRADABLE");
4bytes32 constant REG_D = keccak256("REG_D");
5bytes32 constant REG_S = keccak256("REG_S");

Partition Operations

Balance Queries:

1// Balance in specific partition
2function balanceOfByPartition(bytes32 partition, address holder)
3 public view returns (uint256);
4
5// All partitions held by address
6function partitionsOf(address holder)
7 public view returns (bytes32[] memory);
8
9// Total balance across all partitions
10function balanceOf(address holder)
11 public view returns (uint256);

Transfers:

1function transferByPartition(
2 bytes32 partition,
3 address to,
4 uint256 value,
5 bytes calldata data
6) public returns (bytes32);

The return value indicates the destination partition, which may differ from the source. For example, tokens transferred from a "LOCKED" partition might arrive in the recipient's "TRADABLE" partition if the lock-up period has expired.

Real-World Partition Structures

Equity Token Example:

1├── FOUNDERS (4-year vesting, 1-year cliff)
2├── EMPLOYEES (standard vesting schedule)
3├── SERIES_A (investor lock-up until IPO)
4├── SERIES_B (6-month lock-up)
5└── SECONDARY (freely tradable)

Real Estate Token Example:

1├── PHASE_A_CONSTRUCTION (non-transferable, no dividends)
2├── PHASE_B_OPERATING (transferable, receives rent distributions)
3└── PHASE_C_SALE (liquidation preference)

Jurisdictional Structure:

1├── REG_D_US (1-year lock-up, accredited investors only)
2├── REG_S_INTL (40-day lock-up, non-US only)
3└── PUBLIC (post-registration, freely tradable)

Partition Lifecycle

Creation: Partitions are typically created by authorized issuers when minting new tokens:

1function issueByPartition(
2 bytes32 partition,
3 address holder,
4 uint256 value,
5 bytes calldata data
6) external onlyIssuer;

Migration: Tokens can move between partitions based on time or events:

1function migratePartition(
2 address holder,
3 bytes32 fromPartition,
4 bytes32 toPartition,
5 uint256 value
6) external onlyController;

For time-based unlocks (vesting, lock-up expiry), implementations often use:

  • Automatic migration on transfer attempt (lazy evaluation)
  • Batch migration by controller (gas-efficient for many holders)
  • Holder-initiated claim (user pays gas)

Redemption: When securities are redeemed (buyback, liquidation), partitions may have priority:

1function redeemByPartition(
2 bytes32 partition,
3 uint256 value,
4 bytes calldata data
5) external;

Partition Metadata

Beyond balances, partitions often carry metadata:

1struct PartitionInfo {
2 uint256 lockExpiry; // Timestamp when transfers unlock
3 bool transferable; // Whether transfers are currently allowed
4 uint256 dividendMultiplier; // e.g., 200 = 2x dividends
5 bytes32 destinationPartition; // Where tokens go on transfer
6 address complianceModule; // Partition-specific compliance rules
7}
8
9mapping(bytes32 => PartitionInfo) public partitionInfo;

Security Considerations

Partition Key Collisions: If partition keys are derived from user input without proper validation, attackers might create partitions that collide with or mimic legitimate partitions. Always validate partition keys against an allowlist or restrict partition creation to authorized roles.

Default Partition Bypass: ERC-1410 tokens often implement ERC-20 compatibility by routing transfer() calls through a default partition. If the default partition has different (more permissive) rules than named partitions, this creates a bypass vector. Ensure all transfer paths enforce consistent compliance checks.

Partition Enumeration: The partitionsOf() function reveals which share classes a holder owns. In some contexts, this leaks sensitive information (e.g., that someone holds locked founder shares). Consider privacy implications and whether partition membership should be obfuscated.

Cross-Partition State Consistency: When tokens migrate between partitions, ensure atomic state updates. A failed migration that debits the source but doesn't credit the destination causes token loss.

Controller Partition Powers: The controller role can typically force transfers across any partition. Ensure controller actions are properly logged and subject to timelocks for high-value operations.

Gas Optimization

Lazy Partition Creation: Don't add partitions to _partitionsOf until actually needed. Check if balance is non-zero before array operations.

Partition Limits: Cap the number of partitions per holder to prevent gas griefing on enumeration. A reasonable limit might be 10-20 partitions per address.

Batch Operations: For issuer operations affecting many holders, provide batch functions that amortize base transaction costs:

1function batchIssueByPartition(
2 bytes32 partition,
3 address[] calldata holders,
4 uint256[] calldata values
5) external onlyIssuer;

Partition Caching: Cache frequently-accessed partition data in memory during complex operations rather than repeatedly reading from storage.

Audit Checklist for Token Partitions

  1. Partition Creation: Who can create new partitions? Is there an allowlist?
  2. Balance Integrity: Do partition balances sum to total balance correctly?
  3. Transfer Paths: Do ALL transfer functions respect partition rules?
  4. Migration Logic: Is partition migration atomic and properly authorized?
  5. Enumeration Bounds: Are there limits on partitions per address?
  6. Metadata Consistency: Is partition metadata immutable or properly governed?
  7. Event Emissions: Are partition changes properly logged?
  8. Controller Access: What partition operations can controllers perform?

Token partitions represent the technical foundation for sophisticated security token architectures. They enable regulatory compliance to be encoded directly into token storage rather than enforced through external systems, but require careful implementation to avoid security vulnerabilities and gas inefficiencies.

Need expert guidance on Token Partition?

Our team at Zealynx has deep expertise in blockchain security and DeFi protocols. Whether you need an audit or consultation, we're here to help.

Get a Quote

oog
zealynx

Subscribe to Our Newsletter

Stay updated with our latest security insights and blog posts

© 2024 Zealynx