Skip to content

PT20Factory

Deterministic CREATE2 deployer for monthly PT20 contracts. One PT20 per maturity, salt = keccak256(maturity).

contracts/src/platforms/venice/PT20Factory.sol

Inherits

Ownable2Step

Constants

NameValue
ROLE_DELAY7 days

Immutables

NameType
underlyingaddress (kDIEM)

Mutable state

NamePurpose
lockerSet into each freshly deployed PT20 as its immutable Locker
pendingLocker, lockerEffectiveAtRotation buffer
ptOf[maturity]Mapping maturity → PT20 address (or 0 if not deployed)
allPTs[]Flat list of all deployed PTs in mint order

Wiring setters

solidity
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

solidity
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 (LockerNotSet error).
  • 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 to allPTs.
    • Emits PTDeployed(maturity, pt, locker, name, symbol).

Views

solidity
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-getter

Events

LockerActivated, LockerProposed, LockerChangeCancelled,
PTDeployed

Errors

ZeroAddress, MaturityInPast, MaturityNotFirstOfMonth, LockerNotSet,
AlreadySet, NoPending, TooEarly

CREATE2 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 locker as 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.

Released under the MIT License.