Skip to content

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

NameValue
ROLE_DELAY7 days

Mutable state

Allowlists

MappingPurpose
isLocker[address]Can call mint()
isRedeemer[address]Can call burn() (own balance only)

Bootstrap flags

FlagPurpose
lockerBootstrappedTrue after first addLockerInitial call
redeemerBootstrappedTrue after first addRedeemerInitial call

Pending changes (per target address)

MappingRecords
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)

solidity
function addLockerInitial(address locker) external onlyOwner;
function addRedeemerInitial(address redeemer) external onlyOwner;
  • Each can be called exactly once (gated by the *Bootstrapped flag).
  • Cross-check: IRoleWiringKDIEM(addr).kDiem() == address(this).

Locker rotation (7-day timelock)

solidity
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

solidity
function mint(address to, uint256 amount) external onlyLocker;

Mints amount kDIEM to to. Caller must be in the isLocker allowlist.

Burn

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

Events

LockerAdded, LockerRemoved,
RedeemerAdded, RedeemerRemoved,
LockerAddProposed, LockerAddCancelled,
LockerRemoveProposed, LockerRemoveCancelled,
RedeemerAddProposed, RedeemerAddCancelled,
RedeemerRemoveProposed, RedeemerRemoveCancelled

Errors

NotLocker, NotRedeemer, ZeroAddress,
AlreadySet, AlreadyMember, NotMember,
NoPending, TooEarly, WiringMismatch, NotSelfBurn

Why 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).

Released under the MIT License.