PT20Factory
Deterministic CREATE2 deployer for monthly PT20 contracts. One PT20 per maturity, salt = keccak256(maturity).
contracts/src/platforms/venice/PT20Factory.sol
Inherits
Ownable2Step
Constants
| Name | Value |
|---|---|
ROLE_DELAY | 7 days |
Immutables
| Name | Type |
|---|---|
underlying | address (kDIEM) |
Mutable state
| Name | Purpose |
|---|---|
locker | Set into each freshly deployed PT20 as its immutable Locker |
pendingLocker, lockerEffectiveAt | Rotation buffer |
ptOf[maturity] | Mapping maturity → PT20 address (or 0 if not deployed) |
allPTs[] | Flat list of all deployed PTs in mint order |
Wiring setters
function setLockerInitial(address locker_) external onlyOwner;
function proposeLocker(address locker_) external onlyOwner;
function activateLocker() external;
function cancelLockerChange() external onlyOwner;No cross-check (PT20.locker is set at deploy time, so chicken-and-egg).
PT20 deployment
function getOrCreate(uint64 maturity) external returns (address pt);Permissionless. Anyone can pre-deploy a future PT20 for liquidity setup, DEX listing, indexing, etc.
Constraints:
locker != address(0)— factory must be wired first (LockerNotSeterror).maturity > block.timestamp(MaturityInPast).DateUtils.isFirstOfMonth(maturity)(MaturityNotFirstOfMonth).
Behaviour:
- If
ptOf[maturity] != 0: returns existing PT20 (idempotent). - Otherwise:
salt = keccak256(abi.encode(maturity))pt = new PT20{salt: salt}(factory, underlying, locker, maturity, name, symbol)- Stores
ptOf[maturity] = pt, pushes toallPTs. - Emits
PTDeployed(maturity, pt, locker, name, symbol).
Views
function ptCount() external view returns (uint256);
function allPTs(uint256 index) external view returns (address); // auto-getter
function ptOf(uint64 maturity) external view returns (address); // auto-getterEvents
LockerActivated, LockerProposed, LockerChangeCancelled,
PTDeployedErrors
ZeroAddress, MaturityInPast, MaturityNotFirstOfMonth, LockerNotSet,
AlreadySet, NoPending, TooEarlyCREATE2 determinism
The PT20 address for a given maturity is fully determined by:
- The factory address.
keccak256(abi.encode(maturity))salt.- PT20's bytecode (which includes
lockeras a constructor arg, but bytecode is the bytecode — constructor args do not enter the address calculation).
This means integrators can preinference the PT20 address for a future maturity before it's deployed.
Different factories produce different addresses for the same maturity (factory address is part of CREATE2 derivation). If you ever redeploy PT20Factory (you shouldn't — there's no rotation path), addresses for the same maturity will differ.
Locker rotation semantics
When factory.locker is rotated via timelock:
- PTs deployed before rotation keep their original Locker (immutable per PT20).
- PTs deployed after rotation use the new Locker.
So a v2 Locker can mint into newly-deployed kDIEM-AUG2026, but kDIEM-JUL2026 (deployed under v1) keeps accepting deposits only from v1. The kDIEM allowlist must keep v1 enabled until all old PTs reach maturity.