PYUSDX Compliance & Security
Roles
Access to PYUSDX operations is governed by role-based access control:
| Role | Holder | Powers |
|---|---|---|
DEFAULT_ADMIN_ROLE | Admin | Grant/revoke all roles, set earner manager |
ISSUER_ROLE | IssuerGateway | mint, burn |
PAUSER_ROLE | Pauser | Pause/unpause all transfers |
FREEZE_MANAGER_ROLE | Freeze manager | Freeze/unfreeze accounts |
FORCED_TRANSFER_MANAGER_ROLE | Forced transfer manager | Seize funds from frozen accounts |
RATE_LIMIT_MANAGER_ROLE | Rate limit manager | Set per-issuer rate limits |
The earner manager is a single address (not a role) that controls the yield system; see Token & yield.
Access-control hierarchy
DEFAULT_ADMIN_ROLEcan grant or revoke all roles, set the earner manager (PYUSDX), set the fallback recipient (Portal), and set the mint delay/TTL (IssuerGateway).ISSUER_ROLE(held by theIssuerGateway) canmint()andburn()on PYUSDX.- The earner manager can
setAccountInfo()to configure earners,distributeReward()to mint to any account, and receives yield fees.
Freeze system
- Accounts can be frozen and unfrozen by
FREEZE_MANAGER_ROLE. - Frozen accounts cannot transfer, receive, or claim yield, and cannot be party to an allowance grant:
approveand EIP-2612permitrevert when either the owner or the spender is frozen. - Freezing an earner automatically claims yield and stops earning first.
- Forced transfers can seize funds from frozen accounts.
Forced transfer
_forceTransfer(frozenAccount, recipient, amount):
- Is only callable by
FORCED_TRANSFER_MANAGER_ROLE. - Requires
frozenAccountto be frozen andrecipientto not be frozen. - Frozen accounts are always non-earning (freeze stops earning first).
- Subtracts from the frozen account's balance and adds to the recipient (earner-aware).
- The seizable amount is the full
balance, including any yield that materialized into the account at freeze time via_beforeFreeze(see pause/freeze-time yield behavior).
Pause system
- PYUSDX: a single global pause blocks all transfers, mints, burns, and claims.
setAccountInforemains callable as an emergency lever (with yield materialization to the earner's own balance). - Portal: separate send and receive pause states (see Cross-chain (Portal)).
- SwapFacility / Extensions: a single global pause per contract.
Known pause-time behaviors
setAccountInfowhile paused: yield materializes to the earner's balance, and the fee is forfeited.claimYieldonYieldToOne: succeeds while paused, but the minted tokens cannot transfer until unpause.Portalsend and receive are independently pausable.- All standard transfers are blocked during pause.
Upgradeability
All core contracts (PYUSDX, IssuerGateway, Portal, SwapFacility, ExtensionFactory, ExtensionBeacon, LayerZeroBridgeAdapter) sit behind OpenZeppelin's TransparentUpgradeableProxy, deployed deterministically through CREATE3. Each proxy has a dedicated ProxyAdmin that owns the upgrade entry point; calls from the admin are routed to the proxy's admin surface, and all other calls fall through to the implementation.
- Implementation contracts call
_disableInitializers()in the constructor. - Initializer functions use the
initializeroronlyInitializingmodifiers and explicitly chain every inherited__X_initinitializer (for example__AccessControl_init), so no parent initialization is skipped. - Storage uses ERC-7201 namespaced locations to avoid collisions across upgrades.
Extensions (YieldToOne, MultiMint) use the beacon proxy pattern instead. Each extension type has its own beacon, so a beacon upgrade atomically rolls all instances of that type forward (subject to per-extension version pinning; see Extensions).
Reentrancy protection
Two mechanisms protect against reentrancy:
- ReentrancyLock (transient storage): used by
PortalandSwapFacility. It storesmsg.senderas the lock, enabling caller resolution through the guard. - Checks-Effects-Interactions: all state changes occur before external calls.
Cross-chain security
- Message IDs prevent replay attacks across chains: each
messageIdcan be processed at most once on the destination chain. - Bridge adapters must be explicitly registered per destination chain.
- The fallback recipient prevents frozen-account reverts from stranding bridged funds.
- Failed wraps gracefully degrade to a direct PYUSDX transfer.
Deployed contract addresses are listed on the PYUSDX platform deployments page.