[VPD-613] Venus Pendle PT Fixed Rate Vault#53
Open
Debugger022 wants to merge 57 commits intodevelopfrom
Open
Conversation
…VaultAdapter contract - Added IPendlePTVaultAdapter interface to define the contract's structure and functions. - Refactored PendlePTVaultAdapter to implement the new interface, ensuring compliance with the defined methods. - Removed redundant comments and organized code for better readability. - Updated error handling and event emissions to align with the new interface specifications.
…nd remove obsolete interfaces
- add WBNB aggregator path coverage in PendlePTVaultAdapter fork test - enhance Pendle API to support aggregator routing for token swaps
- Extract inline PT→token swap logic from withdraw() into a dedicated _swapPtToToken() internal function for consistency - Change all private helper functions to internal for uniform visibility
- Added withdraw functionality via adapter with support for direct and aggregator-routed paths. - Implemented error handling for withdraw cases including zero amounts and market status checks. - Introduced redeemAtMaturity functionality with support for direct redemption and aggregator routing. - Enhanced tests for both withdraw and redeemAtMaturity to cover various scenarios and edge cases. - add related api calls
- Derive comptroller from vToken in addMarket (removes manual param, eliminates misconfiguration risk) and validate vToken underlying matches the derived PT address - Add idempotency guards to activateMarket/deactivateMarket to prevent redundant state transitions - Add sweepNative admin function to recover native BNB sent to the contract (complements existing sweepTokens for ERC-20) - Remove unused IWBNB interface and enterMarketBehalf from IVenusComptroller; add comptroller() view to IVenusVToken - Reorganize contract sections (CORE — DEPOSIT / WITHDRAW & REDEEM) and improve NatSpec documentation throughout
Extract the monolithic test file into a modular folder structure under pendlePTVaultAdapter/ with dedicated spec files for each concern: - admin.spec.ts: addMarket, de/activate, pause, sweep, ownership - deposit.spec.ts: ERC-20 deposits (direct mintSy + aggregator) and native BNB deposits with refund handling - withdraw.spec.ts: pre-maturity withdrawals via Pendle AMM sell - redeemAtMaturity.spec.ts: post-maturity PT redemption via SY.redeem - viewFunctions.spec.ts: getMarketConfig, getAllMarkets, isMatured, etc. Shared utilities split into constants, fixtures (baseFixture, depositedFixture), helpers (oracle tolerance, slisBNB minting, dummy params), and pendleApi (swap/redeem param fetching from Pendle API). index.spec.ts acts as a single entry point with one forking() call to avoid multiple hardhat_reset invocations that would invalidate loadFixture snapshots across spec files.
…contract structure
…leanup
- Add complete redeemAtMaturity fork tests (slisBNB, WBNB, native BNB output)
- Add AggregatorMock to bypass KyberSwap executor signature verification
in fork tests (deployed via hardhat_setCode at router address)
- Extract IPancakeRouterV2 interface into separate file (one-contract-per-file)
- Fix function ordering and gas-strict-inequalities linter warnings in adapter
- Replace magic numbers with BSC_CHAIN_ID constant across all test files
- Rename userClisbnbBefore/After to userSlisbnbBefore/After for consistency
- Fix misleading test comments (tokensOut/tokensIn → accurate Pendle terminology)
- Remove unused imports and add missing NatSpec documentation
Balance delta pattern for incoming token transfers: - deposit(): replaced pre-transfer validation (input.netTokenIn == amount) with post-transfer balance delta (balanceAfter - balanceBefore). The actual received amount is validated against input.netTokenIn, correctly handling fee-on-transfer tokens. - withdraw() / redeemAtMaturity(): use balance delta around _redeemVTokens (ptBefore/ptAfter) to capture only the PT received from the current redeem, rather than reading the full contract balance which could include pre-existing tokens from accidental transfers. - _swapToPt(): simplified to approve input.netTokenIn directly instead of reading balanceOf, since deposit() now validates the delta matches. PT dust sweep after Venus mint: - deposit() and depositNative() now sweep PT dust back to the user after _mintVTokens, in case Venus mint rounds down. Previously only tokenIn dust was swept in deposit(), and no dust sweep existed in depositNative(). Why _sweepDust itself was not changed: - _sweepDust transfers OUT (empties the adapter's balance to the user). Balance delta is needed for INCOMING transfers to know exactly how much was received. For outgoing transfers, balanceOf correctly represents what remains to send — after safeTransfer the adapter holds zero. Accidental direct transfers of significant value are handled separately by the admin sweepTokens/sweepNative rescue functions.
…d fix fork tests
Contract changes (PendlePTVaultAdapter.sol):
- _sweepDust and _refundNativeDust now use balance-delta pattern (snapshot
before vs current) instead of sweeping the entire adapter balance. This
prevents pre-existing token balances from leaking to the next caller.
- Added PT dust sweep after swap/redeem in withdraw() and redeemAtMaturity()
for parity with deposit flows — any unconsumed PT is returned to the user.
- Wrapped deposit() body in a scoping block to fix "Stack too deep" caused
by the additional ptBalanceBefore local variable.
Event redesign (IPendlePTVaultAdapter.sol):
- Indexed addresses users will filter by (pendleMarket, user, tokenIn/tokenOut)
instead of uint256 ptAmount which is rarely used as a filter key.
- Reordered Withdrawn and RedeemedAtMaturity params to group indexed fields
first for readability.
Test fixes:
- deposit.spec.ts: added missing approve() before the InputAmountMismatch
error test so safeTransferFrom succeeds and the mismatch check is reached.
- pendleApi.ts: strip signed limit order fills (normalFills/flashFills) from
Pendle API responses in getPendlePtToTokenParams. Live market-maker
signatures are invalid on forked chains ("LOP: bad signature") because
the order nonces don't exist at the forked block. Pure AMM routing is
used instead, which works reliably on forks.
- Transfer, zero-check, and event now read from input.netTokenIn - Balance-delta safety check retained as defense-in-depth - refactor deposit test
- Add FORK_MAINNET export to test utils - Regenerate yarn.lock with @pendle/core-v2 and axios dependencies
…duce SLOADs - Cache config.pt, config.vToken, and config.yt into local stack variables in deposit, depositNative, withdraw, and redeemAtMaturity to avoid redundant warm SLOADs (~97 gas saved per eliminated SLOAD) - Extract _swapToPtNative internal helper from depositNative to resolve stack-too-deep compilation error caused by additional local variables sharing the stack frame with the inline swapExactTokenForPt call - No behavioral changes; all existing tests pass unchanged
…in deposit flows - A malicious limitRouter can inflate the netPtOut return value from swapExactTokenForPt, allowing an attacker to mint vTokens using pre-existing PT balance in the adapter - Compute actual PT received via balance delta (balanceAfter - balanceBefore) instead of trusting swapExactTokenForPt return value - Apply fix to both deposit and depositNative flows - Remove unused return values from _swapToPt and _swapToPtNative - Pendle Router still enforces minPtOut internally for slippage protection
- Pendle's epsSkipMarket threshold can send residual SY to the adapter instead of routing it through the AMM during limit order fills - Add _sweepDust for SY token in both deposit and depositNative flows - Use config.sy directly (no local variable) to avoid stack-too-deep
[VPD-744] Venus Pendle PT Fixed Rate Vault Hashdit Audit
[VPD-738] Venus Pendle PT Fixed Rate Vault Certik Audit
Add proxy and implementation deployment records for the PendlePTVaultAdapter on BSC Mainnet.
|
fred-venus
approved these changes
Mar 13, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
PendlePTVaultAdapter, a universal upgradeable adapter that wraps Pendle PT swaps and Venus Core deposit/redeem into single transactionsArchitecture
DEPOSIT: tokenIn → Pendle Router (swapExactTokenForPt) → PT → Venus (mintBehalf) → vTokens to user
WITHDRAW: vTokens → Venus (redeemBehalf) → PT → Pendle Router (swapExactPtForToken) → tokenOut to user
REDEEM: vTokens → Venus (redeemBehalf) → PT → Pendle Router (redeemPyToToken, 1:1) → tokenOut to user
Key Design Decisions
TokenInput.tokenInaddMarket()derives PT/SY/YT/maturity from Pendle market and comptroller from vToken to prevent misconfigurationAccessControlledV8+PausableUpgradeable+ReentrancyGuardUpgradeablewithuint256[48] __gapContracts
PendlePTVaultAdapter.solIPendlePTVaultAdapter.solIVenusVToken.solIVenusComptroller.solAggregatorMock.solIPancakeRouterV2.solDeployment
deploy/019-deploy-pendle-pt-vault-adapter.tsinitialize(accessControlManager)PENDLE_ROUTER,COMPTROLLER(immutables)NormalTimelockon mainnet,deployeron hardhat/testnetaddMarket()calls done separately via governanceTest Coverage
Fork tests pinned at BSC block
83040087with modular test structure:admin.spec.tsdeposit.spec.tswithdraw.spec.tsredeemAtMaturity.spec.tsviewFunctions.spec.tsAggregatorMock (test infrastructure)
Post-maturity tests require ~1.5yr time travel, which expires KyberSwap aggregator calldata (embedded DEX deadlines + executor signatures).
AggregatorMockis deployed viahardhat_setCodeat the KyberSwap router address to bypass this — it performs a direct PancakeSwap V2 swap instead.Dependencies Added
@pendle/core-v2— Pendle Router interfaces and type definitionsaxios(devDependency) — Pendle API integration for fetching swap parameters in testsTest plan
FORKED_NETWORK=bscmainnet npx hardhat test tests/hardhat/Fork/pendlePTVaultAdapter/index.spec.ts