PYUSDX

PYUSDX Compliance & Security

PYUSDX compliance primitives and security model: roles, account freezing, forced transfers, pausing, upgradeability, and reentrancy protection.

Roles

Access to PYUSDX operations is governed by role-based access control:

RoleHolderPowers
DEFAULT_ADMIN_ROLEAdminGrant/revoke all roles, set earner manager
ISSUER_ROLEIssuerGatewaymint, burn
PAUSER_ROLEPauserPause/unpause all transfers
FREEZE_MANAGER_ROLEFreeze managerFreeze/unfreeze accounts
FORCED_TRANSFER_MANAGER_ROLEForced transfer managerSeize funds from frozen accounts
RATE_LIMIT_MANAGER_ROLERate limit managerSet 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_ROLE can 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 the IssuerGateway) can mint() and burn() 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: approve and EIP-2612 permit revert 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 frozenAccount to be frozen and recipient to 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. setAccountInfo remains 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

  1. setAccountInfo while paused: yield materializes to the earner's balance, and the fee is forfeited.
  2. claimYield on YieldToOne: succeeds while paused, but the minted tokens cannot transfer until unpause.
  3. Portal send and receive are independently pausable.
  4. 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 initializer or onlyInitializing modifiers and explicitly chain every inherited __X_init initializer (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:

  1. ReentrancyLock (transient storage): used by Portal and SwapFacility. It stores msg.sender as the lock, enabling caller resolution through the guard.
  2. Checks-Effects-Interactions: all state changes occur before external calls.

Cross-chain security

  • Message IDs prevent replay attacks across chains: each messageId can 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.

Copyright © M0 Foundation 2026