@@ -7,11 +7,13 @@ import {TokenController} from "./abstract/tokens/TokenController.sol";
77import {LibMintAuth} from "../libs/request/LibMintAuth.sol " ;
88import {IERC20 } from "../interfaces/integrations/IERC20.sol " ;
99import {IRequest} from "../interfaces/request/IRequest.sol " ;
10+ import {IPositionManagerRequest} from "../interfaces/request/IPositionManagerRequest.sol " ;
1011import {IRequestCallback} from "../interfaces/request/IRequestCallback.sol " ;
12+ import {IPositionManagerRequestCallback} from "../interfaces/request/IPositionManagerRequestCallback.sol " ;
1113import {ITokenController} from "../interfaces/request/ITokenController.sol " ;
1214import {Offer} from "../interfaces/request/IOfferReceiver.sol " ;
1315import {Initializable} from "lib/solady/src/utils/Initializable.sol " ;
14- import {Ownable } from "lib/solady/src/auth/Ownable .sol " ;
16+ import {OwnableRoles } from "lib/solady/src/auth/OwnableRoles .sol " ;
1517import {FixedPointMathLib} from "lib/solady/src/utils/FixedPointMathLib.sol " ;
1618import {ReentrancyGuardTransient} from "lib/solady/src/utils/ReentrancyGuardTransient.sol " ;
1719import {SafeTransferLib} from "lib/solady/src/utils/SafeTransferLib.sol " ;
@@ -23,7 +25,7 @@ import {EIP712} from "lib/solady/src/utils/EIP712.sol";
2325/// - **OfferReceiver**: Validates and processes signed offers using EIP-712 signatures
2426/// - **VaultController**: Manages PT/YT tokens with ERC4626-style redemptions
2527/// - **Initializable**: Supports initialization for proxy deployments
26- /// - **Ownable **: Restricts admin functions to the contract owner
28+ /// - **OwnableRoles **: Restricts admin functions to the contract owner and allows for the "puller" role to pull funds from the contract.
2729/// - **ReentrancyGuard**: Prevents reentrancy attacks during offer consumption
2830///
2931/// Deployment Options:
@@ -44,16 +46,22 @@ import {EIP712} from "lib/solady/src/utils/EIP712.sol";
4446/// 2. Owner calls `consume()` with the offer, signature, and PT amount to fulfill
4547/// 3. The maker's `onRequestConsumed` callback is invoked to prepare funds
4648/// 4. Assets are transferred from owner and PT/YT tokens are minted to the maker
47- contract Request is IRequest , OfferReceiver , VaultController , Initializable , Ownable , ReentrancyGuardTransient {
49+ contract Request is IRequest , OfferReceiver , VaultController , Initializable , OwnableRoles , ReentrancyGuardTransient {
4850 using FixedPointMathLib for uint256 ;
4951 using SafeTransferLib for address ;
5052 using LibMintAuth for address ;
5153
54+ /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
55+ /* CONSTANTS */
56+ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
57+
58+ uint256 internal constant _ROLE_PULLER = _ROLE_0;
59+
5260 /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
5361 /* ERRORS */
5462 /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
5563
56- /// @dev The request has already been repaid, preventing further calls to `setRepaid()`.
64+ /// @dev The request has already been repaid, preventing further calls to `setRepaid()`, `pullFunds()`, and `repay()` .
5765 error AlreadyRepaid ();
5866
5967 /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
@@ -64,13 +72,15 @@ contract Request is IRequest, OfferReceiver, VaultController, Initializable, Own
6472 /// @dev Uses ERC-7201 namespaced storage pattern for proxy compatibility. All fields are grouped
6573 /// and accessed via a fixed storage slot to prevent collisions with inherited contracts.
6674 /// @param asset The address of the underlying ERC20 asset (e.g., USDC)
75+ /// @param repaymentDeadline The time at which repayments are unlocked regardless of whether repaid is true or false
6776 /// @param repaid Whether the request has been repaid, enabling withdrawals
6877 /// @param ptToken The address of the Principal Token contract
6978 /// @param ytToken The address of the Yield Token contract
7079 /// @param name The base name for the PT/YT tokens (prefixed with "PT-" / "YT-")
7180 /// @param symbol The base symbol for the PT/YT tokens (prefixed with "PT-" / "YT-")
7281 struct RequestStorage {
7382 address asset;
83+ uint64 repaymentDeadline;
7484 bool repaid;
7585 address ptToken;
7686 address ytToken;
@@ -101,28 +111,34 @@ contract Request is IRequest, OfferReceiver, VaultController, Initializable, Own
101111 /// @notice Initializes the Request contract with all required parameters.
102112 /// @dev Can only be called once due to the `initializer` modifier. Sets up the contract owner,
103113 /// underlying asset, PT/YT token addresses, and metadata. The contract starts in a non-repaid
104- /// state where withdrawals are disabled.
114+ /// state where withdrawals are disabled until either setRepaid() is called or the repayment deadline passes .
105115 /// @param owner_ The address that will own the contract and have admin privileges
116+ /// @param puller_ The address that will have the puller role
106117 /// @param asset_ The address of the underlying ERC20 asset (e.g., USDC)
107118 /// @param ptToken_ The address of the deployed Principal Token contract
108119 /// @param ytToken_ The address of the deployed Yield Token contract
109120 /// @param name_ The base name for the tokens (will be prefixed with "PT-" / "YT-")
110121 /// @param symbol_ The base symbol for the tokens (will be prefixed with "PT-" / "YT-")
122+ /// @param repaymentDeadline_ The timestamp after which withdrawals are automatically enabled, regardless of repaid status
111123 function initialize (
112124 address owner_ ,
125+ address puller_ ,
113126 address asset_ ,
114127 address ptToken_ ,
115128 address ytToken_ ,
116129 string memory name_ ,
117- string memory symbol_
130+ string memory symbol_ ,
131+ uint64 repaymentDeadline_
118132 ) public initializer {
119133 RequestStorage storage req = _requestStorage ();
120134 req.asset = asset_;
135+ req.repaymentDeadline = repaymentDeadline_;
121136 req.ptToken = ptToken_;
122137 req.ytToken = ytToken_;
123138 req.name = name_;
124139 req.symbol = symbol_;
125140 _initializeOwner (owner_);
141+ _setRoles (puller_, _ROLE_PULLER);
126142 }
127143
128144 /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
@@ -135,8 +151,11 @@ contract Request is IRequest, OfferReceiver, VaultController, Initializable, Own
135151 }
136152
137153 /// @inheritdoc VaultController
154+ /// @dev Returns true if either the request has been marked as repaid OR the repayment deadline has passed.
155+ /// This allows withdrawals to be enabled automatically after a deadline, even if setRepaid() was never called.
138156 function _canWithdraw () internal view override returns (bool ) {
139- return _requestStorage ().repaid;
157+ RequestStorage storage req = _requestStorage ();
158+ return req.repaid || block .timestamp >= req.repaymentDeadline;
140159 }
141160
142161 /// @inheritdoc ITokenController
@@ -189,16 +208,29 @@ contract Request is IRequest, OfferReceiver, VaultController, Initializable, Own
189208 emit AuthorizedMinting (to, ptAmount, ytAmount);
190209 }
191210
192- /// @inheritdoc IRequest
193- /// @dev Only callable by the owner . This function is used after offers are consumed to
194- /// transfer the collected funds to the borrower (or any designated receiver) . The borrower
211+ /// @inheritdoc IPositionManagerRequest
212+ /// @dev Only callable by the puller role . This function is used after offers are consumed to
213+ /// transfer the collected funds to the puller . The puller
195214 /// is then expected to repay by transferring assets back to the contract before
196215 /// `setRepaid()` is called to enable PT/YT holder withdrawals.
197216 /// Emits a Transfer event from the underlying asset contract.
198217 /// @custom:reverts If the request has been repaid
199- function pullFunds (address receiver , uint256 amount ) external onlyOwner {
218+ function pullFunds (uint256 amount , bytes calldata data ) external onlyRoles (_ROLE_PULLER) {
219+ if (_canWithdraw ()) revert AlreadyRepaid ();
220+ _asset ().safeTransfer (msg .sender , amount);
221+ if (data.length > 0 ) {
222+ IPositionManagerRequestCallback (msg .sender ).onPullFunds (amount, data);
223+ }
224+ }
225+
226+ /// @inheritdoc IPositionManagerRequest
227+ /// @dev Transfers the underlying assets back to the contract. This is purely optional
228+ /// with the given implementation and may be done via a simple transfer.
229+ /// Cannot be called after the request has been repaid (when withdrawals are enabled).
230+ /// @custom:reverts If the request has been repaid (canWithdraw is true)
231+ function repay (uint256 amount ) external {
200232 if (_canWithdraw ()) revert AlreadyRepaid ();
201- _asset ().safeTransfer (receiver , amount);
233+ _asset ().safeTransferFrom ( msg . sender , address ( this ) , amount);
202234 }
203235
204236 /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
0 commit comments