DDMinter
Replaceable economics policy. Computes DD issuance from (amount, maturity, now) and mints DD directly via DD's mint.
contracts/src/platforms/venice/DDMinter.sol
Inherits
Ownable2Step
Constants
| Name | Value |
|---|---|
ROLE_DELAY | 7 days |
Immutables
| Name | Type |
|---|---|
dd | DD |
Mutable state
| Name | Purpose |
|---|---|
locker | Sole authorized caller of issue() |
pendingLocker, lockerEffectiveAt | Rotation buffer |
minLockAmount | Minimum amount per lock (e.g. 0.01 DIEM) |
maxLockHorizon | Maximum seconds-to-maturity (e.g. 400 days) |
Wiring setters
function setLockerInitial(address locker_) external onlyOwner;
function proposeLocker(address locker_) external onlyOwner;
function activateLocker() external;
function cancelLockerChange() external onlyOwner;No cross-check inside DDMinter — Locker.ddMinter is mutable, so chicken-and-egg. Cross-check lives on the Locker side in Locker.proposeDdMinter.
Config
function setConfig(uint256 minLockAmount_, uint64 maxLockHorizon_) external onlyOwner;Instant (no timelock). Small surface: only affects future locks, never existing positions.
Issuance
function quote(uint256 amount, uint64 maturity, uint256 timestamp)
public view
returns (uint256 ddMinted);
function issue(address recipient, uint256 amount, uint64 maturity)
external onlyLocker
returns (uint256 ddMinted);quote formula:
require amount ≥ minLockAmount
require maturity > timestamp
require maturity ≤ timestamp + maxLockHorizon
seconds = maturity - timestamp
days = seconds / 86400 (floor)
if days == 0 then days = 1 (minimum 1 day)
ddMinted = amount × daysissue wraps quote and immediately dd.mint(recipient, ddMinted).
Events
LockerProposed, LockerActivated, LockerChangeCancelled,
ConfigSetErrors
ZeroAddress, NotLocker, ZeroAmount, AmountBelowMinimum,
MaturityInPast, MaturityTooFar,
AlreadySet, NoPending, TooEarlyWhy floor + min 1 day
Earlier ceiling-rounding formula let attackers lock 1 second before a UTC day boundary to get 2 × amount DD instead of 1 × amount. With floor + minimum-1-day:
secondsToMaturity == 1: 1 day issued (minimum).secondsToMaturity == 86399: 1 day issued.secondsToMaturity == 86400: 1 day issued.secondsToMaturity == 86401: 1 day issued.secondsToMaturity == 172800: 2 days issued.
No way to game timestamp positioning for extra DD.
Replaceability
DDMinter is a policy module. Future versions can introduce non-linear issuance (longer-lock bonus tiers, dynamic discounting, anti-MEV adjustments) by:
- Deploying DDMinter v2 with new logic.
- Calling
dd.proposeMinterAdd(v2)→ wait 7d →activateMinterAdd(v2). - Calling
v2.setLockerInitial(currentLocker). - Calling
locker.proposeDdMinter(v2)→ wait 7d →activateDdMinter(). - Optionally calling
dd.proposeMinterRemove(v1)→ wait 7d → activate.
The full transition takes ~3 weeks (two 7-day timelocks back to back, plus buffer). During the transition, both DDMinters are minters of DD; only one is wired into Locker at a time.