Skip to content

Commit 473d4b2

Browse files
fix: pendle audit issues
* fix: add pendle markets registry for market validations and twap * fix: ignore any tracked PTs and LPTs as-reward tokens * refactor: fix broken links to pendle docs --------- Co-authored-by: Sean Casey <[email protected]>
1 parent 1f70103 commit 473d4b2

14 files changed

+745
-503
lines changed

contracts/external-interfaces/IPendleV2MarketFactory.sol

-18
This file was deleted.

contracts/external-interfaces/IPendleV2PtOracle.sol renamed to contracts/external-interfaces/IPendleV2PtAndLpOracle.sol

+2-4
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,9 @@
1111

1212
pragma solidity >=0.6.0 <0.9.0;
1313

14-
/// @title IPendleV2PtOracle Interface
14+
/// @title IPendleV2PtAndLpOracle Interface
1515
/// @author Enzyme Council <[email protected]>
16-
interface IPendleV2PtOracle {
17-
function getPtToAssetRate(address _market, uint32 _duration) external view returns (uint256 ptToAssetRate_);
18-
16+
interface IPendleV2PtAndLpOracle {
1917
function getOracleState(address _market, uint32 _duration)
2018
external
2119
view

contracts/release/extensions/external-position-manager/external-positions/pendle-v2/IPendleV2Position.sol

-10
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,6 @@ interface IPendleV2Position is IExternalPosition {
2222
ClaimRewards
2323
}
2424

25-
function getMarketForPrincipalToken(address _principalTokenAddress)
26-
external
27-
view
28-
returns (address marketAddress_);
29-
30-
function getOraclePricingDurationForMarket(address _marketAddress)
31-
external
32-
view
33-
returns (uint32 pricingDuration_);
34-
3525
function getLPTokens() external view returns (address[] memory lpTokenAddresses_);
3626

3727
function getPrincipalTokens() external view returns (address[] memory principalTokenAddresses_);

contracts/release/extensions/external-position-manager/external-positions/pendle-v2/PendleV2PositionDataDecoder.sol

+3-10
Original file line numberDiff line numberDiff line change
@@ -22,33 +22,28 @@ abstract contract PendleV2PositionDataDecoder {
2222
internal
2323
pure
2424
returns (
25-
address principalTokenAddress_,
2625
IPendleV2Market market_,
27-
uint32 pricingDuration_,
2826
address depositTokenAddress_,
2927
uint256 depositAmount_,
3028
IPendleV2Router.ApproxParams memory guessPtOut_,
3129
uint256 minPtOut_
3230
)
3331
{
34-
return abi.decode(
35-
_actionArgs, (address, IPendleV2Market, uint32, address, uint256, IPendleV2Router.ApproxParams, uint256)
36-
);
32+
return abi.decode(_actionArgs, (IPendleV2Market, address, uint256, IPendleV2Router.ApproxParams, uint256));
3733
}
3834

3935
/// @dev Helper to decode args used during the SellPrincipalToken action
4036
function __decodeSellPrincipalTokenActionArgs(bytes memory _actionArgs)
4137
internal
4238
pure
4339
returns (
44-
IPendleV2PrincipalToken principalTokenAddress_,
4540
IPendleV2Market market_,
4641
address withdrawalTokenAddress_,
4742
uint256 withdrawalAmount_,
4843
uint256 minIncomingAmount_
4944
)
5045
{
51-
return abi.decode(_actionArgs, (IPendleV2PrincipalToken, IPendleV2Market, address, uint256, uint256));
46+
return abi.decode(_actionArgs, (IPendleV2Market, address, uint256, uint256));
5247
}
5348

5449
/// @dev Helper to decode args used during the AddLiquidity action
@@ -57,15 +52,13 @@ abstract contract PendleV2PositionDataDecoder {
5752
pure
5853
returns (
5954
IPendleV2Market market_,
60-
uint32 pricingDuration_,
6155
address depositTokenAddress_,
6256
uint256 depositAmount_,
6357
IPendleV2Router.ApproxParams memory guessPtReceived_,
6458
uint256 minLpOut_
6559
)
6660
{
67-
return
68-
abi.decode(_actionArgs, (IPendleV2Market, uint32, address, uint256, IPendleV2Router.ApproxParams, uint256));
61+
return abi.decode(_actionArgs, (IPendleV2Market, address, uint256, IPendleV2Router.ApproxParams, uint256));
6962
}
7063

7164
/// @dev Helper to decode args used during the AddLiquidity action

contracts/release/extensions/external-position-manager/external-positions/pendle-v2/PendleV2PositionLib.sol

+83-154
Large diffs are not rendered by default.

contracts/release/extensions/external-position-manager/external-positions/pendle-v2/PendleV2PositionParser.sol

+3-3
Original file line numberDiff line numberDiff line change
@@ -43,20 +43,20 @@ contract PendleV2PositionParser is PendleV2PositionDataDecoder, IExternalPositio
4343
)
4444
{
4545
if (_actionId == uint256(IPendleV2Position.Actions.BuyPrincipalToken)) {
46-
(,,, address depositTokenAddress, uint256 depositAmount,,) =
46+
(, address depositTokenAddress, uint256 depositAmount,,) =
4747
__decodeBuyPrincipalTokenActionArgs(_encodedActionArgs);
4848

4949
assetsToTransfer_ = new address[](1);
5050
assetsToTransfer_[0] = __parseTokenAddressInput(depositTokenAddress);
5151
amountsToTransfer_ = new uint256[](1);
5252
amountsToTransfer_[0] = depositAmount;
5353
} else if (_actionId == uint256(IPendleV2Position.Actions.SellPrincipalToken)) {
54-
(,, address withdrawalTokenAddress,,) = __decodeSellPrincipalTokenActionArgs(_encodedActionArgs);
54+
(, address withdrawalTokenAddress,,) = __decodeSellPrincipalTokenActionArgs(_encodedActionArgs);
5555

5656
assetsToReceive_ = new address[](1);
5757
assetsToReceive_[0] = __parseTokenAddressInput(withdrawalTokenAddress);
5858
} else if (_actionId == uint256(IPendleV2Position.Actions.AddLiquidity)) {
59-
(,, address depositTokenAddress, uint256 depositAmount,,) =
59+
(, address depositTokenAddress, uint256 depositAmount,,) =
6060
__decodeAddLiquidityActionArgs(_encodedActionArgs);
6161

6262
assetsToTransfer_ = new address[](1);

contracts/release/extensions/external-position-manager/external-positions/pendle-v2/bases/PendleV2PositionLibBase1.sol

+1-7
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,15 @@ pragma solidity 0.8.19;
1919
/// a numbered PendleV2PositionLibBaseXXX that inherits the previous base.
2020
/// e.g., `PendleV2PositionLibBase2 is PendleV2PositionLibBase1`
2121
abstract contract PendleV2PositionLibBase1 {
22-
event PrincipalTokenAdded(address indexed principalToken, address indexed market);
22+
event PrincipalTokenAdded(address indexed principalToken);
2323

2424
event PrincipalTokenRemoved(address indexed principalToken);
2525

2626
event LpTokenAdded(address indexed lpToken);
2727

2828
event LpTokenRemoved(address indexed lpToken);
2929

30-
event OracleDurationForMarketAdded(address indexed market, uint32 indexed pricingDuration);
31-
3230
address[] internal principalTokens;
3331

3432
address[] internal lpTokens;
35-
36-
mapping(address principalToken => address market) internal principalTokenToMarket;
37-
38-
mapping(address market => uint32 pricingDuration) internal marketToOraclePricingDuration;
3933
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// SPDX-License-Identifier: GPL-3.0
2+
3+
/*
4+
This file is part of the Enzyme Protocol.
5+
6+
(c) Enzyme Council <[email protected]>
7+
8+
For the full license information, please view the LICENSE
9+
file that was distributed with this source code.
10+
*/
11+
12+
pragma solidity >=0.6.0 <0.9.0;
13+
14+
/// @title IPendleV2MarketRegistry Interface
15+
/// @author Enzyme Council <[email protected]>
16+
interface IPendleV2MarketRegistry {
17+
/// @param marketAddress The Pendle market address to register
18+
/// @param duration The TWAP duration to use for marketAddress
19+
struct UpdateMarketInput {
20+
address marketAddress;
21+
uint32 duration;
22+
}
23+
24+
function getMarketOracleDurationForUser(address _user, address _marketAddress)
25+
external
26+
view
27+
returns (uint32 duration_);
28+
29+
function getPtOracleMarketAndDurationForUser(address _user, address _ptAddress)
30+
external
31+
view
32+
returns (address marketAddress_, uint32 duration_);
33+
34+
function getPtOracleMarketForUser(address _user, address _ptAddress)
35+
external
36+
view
37+
returns (address marketAddress_);
38+
39+
function updateMarketsForCaller(UpdateMarketInput[] calldata _updateMarketInputs, bool _skipValidation) external;
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
// SPDX-License-Identifier: GPL-3.0
2+
3+
/*
4+
This file is part of the Enzyme Protocol.
5+
6+
(c) Enzyme Council <[email protected]>
7+
8+
For the full license information, please view the LICENSE
9+
file that was distributed with this source code.
10+
*/
11+
12+
pragma solidity 0.8.19;
13+
14+
import {IDispatcher} from "../../../../../../persistent/dispatcher/IDispatcher.sol";
15+
import {IPendleV2Market} from "../../../../../../external-interfaces/IPendleV2Market.sol";
16+
import {IPendleV2PrincipalToken} from "../../../../../../external-interfaces/IPendleV2PrincipalToken.sol";
17+
import {IPendleV2PtAndLpOracle} from "../../../../../../external-interfaces/IPendleV2PtAndLpOracle.sol";
18+
import {IPendleV2MarketRegistry} from "./IPendleV2MarketRegistry.sol";
19+
20+
/// @title PendleV2MarketRegistry Contract
21+
/// @author Enzyme Council <[email protected]>
22+
/// @notice A contract for the per-user registration of Pendle v2 markets
23+
contract PendleV2MarketRegistry is IPendleV2MarketRegistry {
24+
event MarketForUserUpdated(address indexed user, address indexed marketAddress, uint32 duration);
25+
26+
event PtForUserUpdated(address indexed user, address indexed ptAddress, address indexed marketAddress);
27+
28+
error InsufficientOracleState(bool increaseCardinalityRequired, bool oldestObservationSatisfied);
29+
30+
IPendleV2PtAndLpOracle private immutable PENDLE_PT_AND_LP_ORACLE;
31+
32+
mapping(address => mapping(address => uint32)) private userToMarketToOracleDuration;
33+
mapping(address => mapping(address => address)) private userToPtToLinkedMarket;
34+
35+
constructor(IPendleV2PtAndLpOracle _pendlePtAndLpOracle) {
36+
PENDLE_PT_AND_LP_ORACLE = _pendlePtAndLpOracle;
37+
}
38+
39+
/// @notice Updates the market registry specific to the caller
40+
/// @param _updateMarketInputs An array of market config inputs to set
41+
/// @param _skipValidation True to skip optional validation of _updateMarketInputs
42+
/// @dev See UpdateMarketInput definition for struct param details
43+
function updateMarketsForCaller(UpdateMarketInput[] calldata _updateMarketInputs, bool _skipValidation)
44+
external
45+
override
46+
{
47+
address user = msg.sender;
48+
49+
for (uint256 i; i < _updateMarketInputs.length; i++) {
50+
UpdateMarketInput memory marketInput = _updateMarketInputs[i];
51+
52+
// Does not validate zero-duration, which is a valid oracle deactivation
53+
if (marketInput.duration > 0 && !_skipValidation) {
54+
__validateMarketConfig({_marketAddress: marketInput.marketAddress, _duration: marketInput.duration});
55+
}
56+
57+
// Store the market duration
58+
userToMarketToOracleDuration[user][marketInput.marketAddress] = marketInput.duration;
59+
emit MarketForUserUpdated(user, marketInput.marketAddress, marketInput.duration);
60+
61+
// Handle PT-market link
62+
(, IPendleV2PrincipalToken pt,) = IPendleV2Market(marketInput.marketAddress).readTokens();
63+
bool ptIsLinkedToMarket =
64+
getPtOracleMarketForUser({_user: user, _ptAddress: address(pt)}) == marketInput.marketAddress;
65+
66+
if (marketInput.duration > 0) {
67+
// If new duration is non-zero, cache PT-market link (i.e., always follow the last active market)
68+
69+
if (!ptIsLinkedToMarket) {
70+
userToPtToLinkedMarket[user][address(pt)] = marketInput.marketAddress;
71+
emit PtForUserUpdated(user, address(pt), marketInput.marketAddress);
72+
}
73+
} else if (ptIsLinkedToMarket) {
74+
// If the PT's linked market duration is being set to 0, remove link to the market
75+
76+
// Unlink the PT from the market
77+
userToPtToLinkedMarket[user][address(pt)] = address(0);
78+
emit PtForUserUpdated(user, address(pt), address(0));
79+
}
80+
}
81+
}
82+
83+
/// @dev Helper to validate user-input market config.
84+
/// Only validates the recommended oracle state,
85+
/// not whether duration provides a sufficiently secure TWAP price.
86+
/// src: https://docs.pendle.finance/Developers/Integration/HowToIntegratePtAndLpOracle.
87+
function __validateMarketConfig(address _marketAddress, uint32 _duration) private view {
88+
(bool increaseCardinalityRequired,, bool oldestObservationSatisfied) =
89+
PENDLE_PT_AND_LP_ORACLE.getOracleState({_market: _marketAddress, _duration: _duration});
90+
91+
if (increaseCardinalityRequired || !oldestObservationSatisfied) {
92+
revert InsufficientOracleState(increaseCardinalityRequired, oldestObservationSatisfied);
93+
}
94+
}
95+
96+
///////////////////
97+
// STATE GETTERS //
98+
///////////////////
99+
100+
// EXTERNAL
101+
102+
/// @notice Gets the oracle market and its duration for a principal token, as-registered by the given user
103+
/// @param _user The user
104+
/// @param _ptAddress The principal token
105+
/// @return marketAddress_ The market
106+
/// @return duration_ The duration
107+
function getPtOracleMarketAndDurationForUser(address _user, address _ptAddress)
108+
external
109+
view
110+
returns (address marketAddress_, uint32 duration_)
111+
{
112+
marketAddress_ = getPtOracleMarketForUser({_user: _user, _ptAddress: _ptAddress});
113+
duration_ = getMarketOracleDurationForUser({_user: _user, _marketAddress: marketAddress_});
114+
115+
return (marketAddress_, duration_);
116+
}
117+
118+
// PUBLIC
119+
120+
/// @notice Gets the oracle duration for a market, as-registered by the given user
121+
/// @param _user The user
122+
/// @param _marketAddress The market
123+
/// @return duration_ The duration
124+
function getMarketOracleDurationForUser(address _user, address _marketAddress)
125+
public
126+
view
127+
returns (uint32 duration_)
128+
{
129+
return userToMarketToOracleDuration[_user][_marketAddress];
130+
}
131+
132+
/// @notice Gets the linked market for a principal token, as-registered by the given user
133+
/// @param _user The user
134+
/// @param _ptAddress The principal token
135+
/// @return marketAddress_ The market
136+
function getPtOracleMarketForUser(address _user, address _ptAddress) public view returns (address marketAddress_) {
137+
return userToPtToLinkedMarket[_user][_ptAddress];
138+
}
139+
}

tests/interfaces/external/IPendleV2PtOracle.sol renamed to tests/interfaces/external/IPendleV2PtAndLpOracle.sol

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
// SPDX-License-Identifier: GPL-3.0
22
pragma solidity >=0.6.0 <0.9.0;
33

4-
/// @title IPendleV2PtOracle Interface
4+
/// @title IPendleV2PtAndLpOracle Interface
55
/// @author Enzyme Council <[email protected]>
6-
interface IPendleV2PtOracle {
6+
interface IPendleV2PtAndLpOracle {
77
function getPtToAssetRate(address _market, uint32 _duration) external view returns (uint256 ptToAssetRate_);
88

99
function getOracleState(address _market, uint32 _duration)

tests/interfaces/interfaces.txt

+1
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ IUniswapV3LiquidityPositionLib.sol: UniswapV3LiquidityPositionLib.abi.json
157157
IUniswapV3LiquidityPositionParser.sol: UniswapV3LiquidityPositionParser.abi.json
158158
ITermFinanceV1LendingPositionLib.sol: TermFinanceV1LendingPositionLib.abi.json
159159
ITermFinanceV1LendingPositionParser.sol: TermFinanceV1LendingPositionParser.abi.json
160+
IPendleV2MarketRegistry.sol: PendleV2MarketRegistry.abi.json
160161
IPendleV2PositionLib.sol: PendleV2PositionLib.abi.json
161162
IPendleV2PositionParser.sol: PendleV2PositionParser.abi.json
162163
IMorphoBluePositionLib.sol: MorphoBluePositionLib.abi.json

0 commit comments

Comments
 (0)