// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.24; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; import {IUSDR} from "./interfaces/IUSDR.sol"; /// @title EmergencySettlement — global shutdown, last resort (White Paper v3.2 §7 step 5) /// @notice One-way switch (timelock-gated). After trigger, USDR holders claim a /// pro-rata share of every registered collateral pool at $1 valuation. /// "Loss is bounded, tiered, and transparent — never an all-erasing spiral." contract EmergencySettlement is ReentrancyGuard { using SafeERC20 for IERC20; error NotTimelock(); error NotTriggered(); error AlreadyTriggered(); error NothingToClaim(); IUSDR public immutable usdr; address public immutable timelock; bool public triggered; uint256 public supplySnapshot; address[] public assets; // all collateral + stables registered at trigger time mapping(address => uint256) public assetSnapshot; // balances captured at trigger event Triggered(uint256 supplySnapshot); event Claimed(address indexed holder, uint256 usdrBurned); constructor(address _usdr, address _timelock) { usdr = IUSDR(_usdr); timelock = _timelock; } /// @notice Trigger shutdown. Asset holders (vault, reserve, escrow) must have /// approved/transferred pools here as part of the governance action. function trigger(address[] calldata _assets) external { if (msg.sender != timelock) revert NotTimelock(); if (triggered) revert AlreadyTriggered(); triggered = true; supplySnapshot = usdr.totalSupply(); for (uint256 i = 0; i < _assets.length; i++) { assets.push(_assets[i]); assetSnapshot[_assets[i]] = IERC20(_assets[i]).balanceOf(address(this)); } emit Triggered(supplySnapshot); } /// @notice Burn USDR, receive pro-rata share of every asset in the pool. function claim(uint256 usdrAmount) external nonReentrant { if (!triggered) revert NotTriggered(); if (usdrAmount == 0) revert NothingToClaim(); usdr.burn(msg.sender, usdrAmount); for (uint256 i = 0; i < assets.length; i++) { uint256 share = assetSnapshot[assets[i]] * usdrAmount / supplySnapshot; if (share > 0) IERC20(assets[i]).safeTransfer(msg.sender, share); } emit Claimed(msg.sender, usdrAmount); } }