kDIEM (contract)
Internal escrow ERC-20 that backs the PT20/PT721 principal claims. End users never hold or transfer it directly — the Locker mints it into the PT20 / PT721 contracts during a lock, and the Redeemer burns it during a redeem. This page documents the contract surface.
contracts/src/platforms/venice/kDIEM.sol
Inherits
ERC20 (OpenZeppelin), Ownable2Step
Constants
| Name | Value |
|---|---|
ROLE_DELAY | 7 days |
Mutable state
Allowlists
| Mapping | Purpose |
|---|---|
isLocker[address] | Can call mint() |
isRedeemer[address] | Can call burn() (own balance only) |
Bootstrap flags
| Flag | Purpose |
|---|---|
lockerBootstrapped | True after first addLockerInitial call |
redeemerBootstrapped | True after first addRedeemerInitial call |
Pending changes (per target address)
| Mapping | Records |
|---|---|
pendingLockerAdd[address] | effective-at for adding a locker |
pendingLockerRemove[address] | effective-at for removing a locker |
pendingRedeemerAdd[address] | effective-at for adding a redeemer |
pendingRedeemerRemove[address] | effective-at for removing a redeemer |
Bootstrap setters (one-shot)
function addLockerInitial(address locker) external onlyOwner;
function addRedeemerInitial(address redeemer) external onlyOwner;- Each can be called exactly once (gated by the
*Bootstrappedflag). - Cross-check:
IRoleWiringKDIEM(addr).kDiem() == address(this).
Locker rotation (7-day timelock)
function proposeLockerAdd(address locker) external onlyOwner;
function activateLockerAdd(address locker) external; // permissionless after 7d
function cancelLockerAdd(address locker) external onlyOwner;
function proposeLockerRemove(address locker) external onlyOwner;
function activateLockerRemove(address locker) external;
function cancelLockerRemove(address locker) external onlyOwner;proposeLockerAdd requires !isLocker[locker] (else AlreadyMember) and the cross-check above. proposeLockerRemove requires isLocker[locker] (else NotMember).
Redeemer rotation (7-day timelock)
Same four-function shape as Locker rotation — proposeRedeemerAdd, activateRedeemerAdd, cancelRedeemerAdd, proposeRedeemerRemove, activateRedeemerRemove, cancelRedeemerRemove.
Mint
function mint(address to, uint256 amount) external onlyLocker;Mints amount kDIEM to to. Caller must be in the isLocker allowlist.
Burn
function burn(address from, uint256 amount) external onlyRedeemer;Burns amount kDIEM from from. Caller must be in isRedeemer allowlist AND from == msg.sender (NotSelfBurn otherwise).
This means a hostile redeemer (if somehow added) can only burn its own balance. It cannot reach into PT20 or PT721 escrow and destroy backing for other holders. The only legitimate caller pattern is:
1. Redeemer pulls PT20/PT721 from user
2. Redeemer unwraps to kDIEM (now Redeemer holds kDIEM)
3. Redeemer calls kDiem.burn(address(this), amount)
4. Redeemer queues unstakeEvents
LockerAdded, LockerRemoved,
RedeemerAdded, RedeemerRemoved,
LockerAddProposed, LockerAddCancelled,
LockerRemoveProposed, LockerRemoveCancelled,
RedeemerAddProposed, RedeemerAddCancelled,
RedeemerRemoveProposed, RedeemerRemoveCancelledErrors
NotLocker, NotRedeemer, ZeroAddress,
AlreadySet, AlreadyMember, NotMember,
NoPending, TooEarly, WiringMismatch, NotSelfBurnWhy allowlist (multiple) rather than single address
Smooth migration. Deploy a new Locker v2, propose-and-activate to add it to the allowlist (7 days). Both v1 and v2 now mint kDIEM in parallel. Users gradually migrate to v2 via the UI. After all v1 PTs reach maturity and are redeemed, propose-and-activate to remove v1 from the allowlist (another 7 days).
With a single-address setup, this dual-running phase wouldn't be possible — the moment you swap, v1 stops working and any in-flight lock through v1 reverts.
Invariant
For every PT20 contract pt and for the singleton PT721:
kDIEM.totalSupply()
== sum_over_all_PTs( kDIEM.balanceOf(pt) )
+ kDIEM.balanceOf(PT721)Locker and Redeemer always hold 0 kDIEM between transactions (they only hold it transiently inside lock / redeem).
This invariant is fuzz-tested by Foundry invariant tests (128k random calls × multiple invariants, 0 violations) and Echidna (50k calls, property PASS).