@pendle/core-v2 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/contracts/SuperComposableYield/ISuperComposableYield.sol +1 -5
  2. package/contracts/SuperComposableYield/implementations/RewardManager.sol +2 -2
  3. package/contracts/SuperComposableYield/implementations/SCYBase.sol +2 -2
  4. package/contracts/SuperComposableYield/implementations/SCYBaseWithRewards.sol +4 -7
  5. package/contracts/core/PendleMarket.sol +24 -22
  6. package/contracts/core/PendleMarketFactory.sol +7 -6
  7. package/contracts/core/PendleSCYImpl/PendleBenQiErc20SCY.sol +1 -1
  8. package/contracts/core/PendleSCYImpl/PendleBtrflySCY.sol +2 -7
  9. package/contracts/core/PendleSCYImpl/PendleYearnVaultSCY.sol +2 -7
  10. package/contracts/core/PendleYieldContractFactory.sol +12 -7
  11. package/contracts/core/PendleYieldToken.sol +93 -55
  12. package/contracts/core/router/PendleRouterCoreUpg.sol +35 -21
  13. package/contracts/core/router/PendleRouterProxy.sol +12 -6
  14. package/contracts/core/router/{PendleRouterStatic.sol → PendleRouterStaticUpg.sol} +43 -18
  15. package/contracts/core/router/PendleRouterYTUpg.sol +47 -13
  16. package/contracts/core/router/base/PendleRouterOTBaseUpg.sol +22 -32
  17. package/contracts/core/router/base/PendleRouterSCYAndForgeBaseUpg.sol +1 -1
  18. package/contracts/core/router/base/PendleRouterYTBaseUpg.sol +62 -43
  19. package/contracts/interfaces/IPMarket.sol +4 -2
  20. package/contracts/interfaces/{IPPermissionsV2.sol → IPPermissionsV2Upg.sol} +1 -1
  21. package/contracts/interfaces/IPRouterCore.sol +24 -21
  22. package/contracts/interfaces/IPRouterStatic.sol +10 -1
  23. package/contracts/interfaces/IPRouterYT.sol +16 -4
  24. package/contracts/interfaces/IPYieldContractFactory.sol +4 -0
  25. package/contracts/interfaces/IPYieldToken.sol +2 -4
  26. package/contracts/libraries/SCYIndex.sol +3 -3
  27. package/contracts/libraries/math/LogExpMath.sol +1 -90
  28. package/contracts/libraries/math/MarketApproxLib.sol +336 -94
  29. package/contracts/libraries/math/MarketMathAux.sol +101 -0
  30. package/contracts/libraries/math/MarketMathCore.sol +423 -0
  31. package/contracts/libraries/math/Math.sol +144 -0
  32. package/contracts/{core/misc → periphery}/PendleJoeSwapHelperUpg.sol +2 -2
  33. package/contracts/periphery/{PermissionsV2.sol → PermissionsV2Upg.sol} +2 -2
  34. package/package.json +1 -1
  35. package/typechain-types/IPMarket.ts +22 -11
  36. package/typechain-types/IPRouterCore.ts +68 -58
  37. package/typechain-types/IPRouterStatic.ts +52 -0
  38. package/typechain-types/IPRouterYT.ts +96 -12
  39. package/typechain-types/IPYieldContractFactory.ts +32 -0
  40. package/typechain-types/IPYieldToken.ts +14 -48
  41. package/typechain-types/IPermissionsV2Upg.ts +87 -0
  42. package/typechain-types/ISuperComposableYield.ts +3 -11
  43. package/typechain-types/OwnableUpgradeable.ts +165 -0
  44. package/typechain-types/PendleAaveV3SCY.ts +3 -11
  45. package/typechain-types/PendleBenQiErc20SCY.ts +3 -11
  46. package/typechain-types/PendleBtrflyScy.ts +3 -11
  47. package/typechain-types/PendleMarket.ts +22 -11
  48. package/typechain-types/PendleRouterCoreUpg.ts +66 -48
  49. package/typechain-types/PendleRouterProxy.ts +15 -118
  50. package/typechain-types/PendleRouterStaticUpg.ts +92 -0
  51. package/typechain-types/PendleRouterYTUpg.ts +96 -12
  52. package/typechain-types/PendleYearnVaultScy.ts +3 -11
  53. package/typechain-types/PendleYieldContractFactory.ts +40 -113
  54. package/typechain-types/PendleYieldToken.ts +106 -7
  55. package/typechain-types/PermissionsV2Upg.ts +87 -0
  56. package/typechain-types/SCYBase.ts +3 -11
  57. package/typechain-types/SCYBaseWithRewards.ts +3 -11
  58. package/typechain-types/factories/IPMarket__factory.ts +8 -2
  59. package/typechain-types/factories/IPRouterCore__factory.ts +38 -23
  60. package/typechain-types/factories/IPRouterStatic__factory.ts +29 -0
  61. package/typechain-types/factories/IPRouterYT__factory.ts +65 -6
  62. package/typechain-types/factories/IPYieldContractFactory__factory.ts +38 -0
  63. package/typechain-types/factories/IPYieldToken__factory.ts +1 -20
  64. package/typechain-types/factories/IPermissionsV2Upg__factory.ts +39 -0
  65. package/typechain-types/factories/ISuperComposableYield__factory.ts +0 -5
  66. package/typechain-types/factories/OwnableUpgradeable__factory.ts +78 -0
  67. package/typechain-types/factories/PendleAaveV3SCY__factory.ts +1 -6
  68. package/typechain-types/factories/PendleBenQiErc20SCY__factory.ts +1 -6
  69. package/typechain-types/factories/PendleBtrflyScy__factory.ts +1 -6
  70. package/typechain-types/factories/PendleMarketFactory__factory.ts +1 -1
  71. package/typechain-types/factories/PendleMarket__factory.ts +9 -3
  72. package/typechain-types/factories/PendleRouterCoreUpg__factory.ts +26 -11
  73. package/typechain-types/factories/PendleRouterProxy__factory.ts +14 -67
  74. package/typechain-types/factories/PendleRouterStaticUpg__factory.ts +63 -17
  75. package/typechain-types/factories/PendleRouterYTUpg__factory.ts +66 -7
  76. package/typechain-types/factories/PendleYearnVaultScy__factory.ts +1 -6
  77. package/typechain-types/factories/PendleYieldContractFactory__factory.ts +39 -54
  78. package/typechain-types/factories/PendleYieldToken__factory.ts +53 -2
  79. package/typechain-types/factories/PermissionsV2Upg__factory.ts +39 -0
  80. package/typechain-types/factories/SCYBaseWithRewards__factory.ts +0 -5
  81. package/typechain-types/factories/SCYBase__factory.ts +0 -5
  82. package/typechain-types/hardhat.d.ts +26 -53
  83. package/typechain-types/index.ts +8 -14
  84. package/contracts/core/misc/BoringOwnable.sol +0 -62
  85. package/contracts/core/misc/BoringOwnableUpg.sol +0 -64
  86. package/contracts/libraries/math/FixedPoint.sol +0 -205
  87. package/contracts/libraries/math/MarketMathLib.sol +0 -614
@@ -41,11 +41,7 @@ interface ISuperComposableYield is IERC20Metadata {
41
41
 
42
42
  function updateUserReward(address user) external;
43
43
 
44
- /// @notice if user == msg.sender, receiver can be any addresses
45
- /// else, receiver must be msg.sender
46
- function redeemReward(address user, address receiver)
47
- external
48
- returns (uint256[] memory outAmounts);
44
+ function redeemReward(address user) external returns (uint256[] memory outAmounts);
49
45
 
50
46
  /**
51
47
  * @notice scyIndexCurrent.mulDown(scyBalance) must return the asset balance of the account
@@ -2,11 +2,11 @@
2
2
  pragma solidity 0.8.9;
3
3
  import "../ISuperComposableYield.sol";
4
4
  import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
5
- import "../../libraries/math/FixedPoint.sol";
5
+ import "../../libraries/math/Math.sol";
6
6
 
7
7
  abstract contract RewardManager {
8
8
  using SafeERC20 for IERC20;
9
- using FixedPoint for uint256;
9
+ using Math for uint256;
10
10
 
11
11
  struct GlobalReward {
12
12
  uint256 index;
@@ -4,7 +4,7 @@ import "../ISuperComposableYield.sol";
4
4
  import "./RewardManager.sol";
5
5
  import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
6
6
  import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
7
- import "../../libraries/math/FixedPoint.sol";
7
+ import "../../libraries/math/Math.sol";
8
8
  import "../SCYUtils.sol";
9
9
 
10
10
  /**
@@ -15,7 +15,7 @@ satisfy this restriction is AaveV2's aToken
15
15
  */
16
16
  abstract contract SCYBase is ERC20, ISuperComposableYield {
17
17
  using SafeERC20 for IERC20;
18
- using FixedPoint for uint256;
18
+ using Math for uint256;
19
19
 
20
20
  uint8 private immutable _scyDecimals;
21
21
  uint8 private immutable _assetDecimals;
@@ -5,7 +5,7 @@ import "./RewardManager.sol";
5
5
  import "./SCYBase.sol";
6
6
  import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
7
7
  import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
8
- import "../../libraries/math/FixedPoint.sol";
8
+ import "../../libraries/math/Math.sol";
9
9
 
10
10
  /**
11
11
  # CONDITIONS TO USE THIS PRESET:
@@ -15,7 +15,7 @@ satisfy this restriction is AaveV2's aToken
15
15
  */
16
16
  abstract contract SCYBaseWithRewards is SCYBase, RewardManager {
17
17
  using SafeERC20 for IERC20;
18
- using FixedPoint for uint256;
18
+ using Math for uint256;
19
19
 
20
20
  constructor(
21
21
  string memory _name,
@@ -34,17 +34,14 @@ abstract contract SCYBaseWithRewards is SCYBase, RewardManager {
34
34
  REWARDS-RELATED
35
35
  //////////////////////////////////////////////////////////////*/
36
36
 
37
- function redeemReward(address user, address receiver)
37
+ function redeemReward(address user)
38
38
  public
39
39
  virtual
40
40
  override
41
41
  returns (uint256[] memory outAmounts)
42
42
  {
43
- if (user != msg.sender) require(receiver == user, "invalid receiver");
44
- else require(receiver != address(0), "zero address");
45
-
46
43
  _updateUserReward(user, balanceOf(user), totalSupply());
47
- outAmounts = _doTransferOutRewardsForUser(user, receiver);
44
+ outAmounts = _doTransferOutRewardsForUser(user, user);
48
45
  }
49
46
 
50
47
  function updateGlobalReward() public virtual override {
@@ -10,19 +10,20 @@ import "../interfaces/IPMarketSwapCallback.sol";
10
10
  import "../interfaces/IPMarketAddRemoveCallback.sol";
11
11
 
12
12
  import "../libraries/math/LogExpMath.sol";
13
- import "../libraries/math/FixedPoint.sol";
14
- import "../libraries/math/MarketMathLib.sol";
13
+ import "../libraries/math/Math.sol";
14
+ import "../libraries/math/MarketMathAux.sol";
15
15
 
16
16
  import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
17
17
  import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
18
18
 
19
19
  // solhint-disable reason-string
20
20
  contract PendleMarket is PendleBaseToken, IPMarket, ReentrancyGuard {
21
- using FixedPoint for uint256;
22
- using FixedPoint for int256;
23
- using FixedPoint for uint128;
21
+ using Math for uint256;
22
+ using Math for int256;
23
+ using Math for uint128;
24
24
  using LogExpMath for uint256;
25
- using MarketMathLib for MarketParameters;
25
+ using MarketMathAux for MarketState;
26
+ using MarketMathCore for MarketState;
26
27
  using SafeERC20 for IERC20;
27
28
 
28
29
  string private constant NAME = "Pendle Market";
@@ -75,14 +76,15 @@ contract PendleMarket is PendleBaseToken, IPMarket, ReentrancyGuard {
75
76
  uint256 otUsed
76
77
  )
77
78
  {
78
- MarketParameters memory market = readState();
79
+ MarketState memory market = readState(true);
79
80
  SCYIndex index = SCYIndexLib.newIndex(SCY);
80
81
 
81
82
  uint256 lpToReserve;
82
83
  (lpToReserve, lpToAccount, scyUsed, otUsed) = market.addLiquidity(
83
84
  index,
84
85
  scyDesired,
85
- otDesired
86
+ otDesired,
87
+ true
86
88
  );
87
89
 
88
90
  // initializing the market
@@ -113,9 +115,9 @@ contract PendleMarket is PendleBaseToken, IPMarket, ReentrancyGuard {
113
115
  uint256 lpToRemove,
114
116
  bytes calldata data
115
117
  ) external nonReentrant returns (uint256 scyToAccount, uint256 otToAccount) {
116
- MarketParameters memory market = readState();
118
+ MarketState memory market = readState(true);
117
119
 
118
- (scyToAccount, otToAccount) = market.removeLiquidity(lpToRemove);
120
+ (scyToAccount, otToAccount) = market.removeLiquidity(lpToRemove, true);
119
121
 
120
122
  IERC20(SCY).safeTransfer(receiver, scyToAccount);
121
123
  IERC20(OT).safeTransfer(receiver, otToAccount);
@@ -142,12 +144,13 @@ contract PendleMarket is PendleBaseToken, IPMarket, ReentrancyGuard {
142
144
  ) external nonReentrant returns (uint256 netScyOut, uint256 netScyToReserve) {
143
145
  require(block.timestamp < expiry, "MARKET_EXPIRED");
144
146
 
145
- MarketParameters memory market = readState();
147
+ MarketState memory market = readState(true);
146
148
 
147
149
  (netScyOut, netScyToReserve) = market.swapExactOtForScy(
148
150
  SCYIndexLib.newIndex(SCY),
149
151
  exactOtIn,
150
- block.timestamp
152
+ block.timestamp,
153
+ true
151
154
  );
152
155
  require(netScyOut >= minScyOut, "insufficient scy out");
153
156
  IERC20(SCY).safeTransfer(receiver, netScyOut);
@@ -170,12 +173,13 @@ contract PendleMarket is PendleBaseToken, IPMarket, ReentrancyGuard {
170
173
  ) external nonReentrant returns (uint256 netScyIn, uint256 netScyToReserve) {
171
174
  require(block.timestamp < expiry, "MARKET_EXPIRED");
172
175
 
173
- MarketParameters memory market = readState();
176
+ MarketState memory market = readState(true);
174
177
 
175
178
  (netScyIn, netScyToReserve) = market.swapScyForExactOt(
176
179
  SCYIndexLib.newIndex(SCY),
177
180
  exactOtOut,
178
- block.timestamp
181
+ block.timestamp,
182
+ true
179
183
  );
180
184
  require(netScyIn <= maxScyIn, "scy in exceed limit");
181
185
  IERC20(OT).safeTransfer(receiver, exactOtOut);
@@ -193,14 +197,10 @@ contract PendleMarket is PendleBaseToken, IPMarket, ReentrancyGuard {
193
197
  /// @dev this function is just a place holder. Later on the rewards will be transferred to the liquidity minining
194
198
  /// instead
195
199
  function redeemScyReward() external returns (uint256[] memory outAmounts) {
196
- outAmounts = ISuperComposableYield(SCY).redeemReward(
197
- address(this),
198
- IPMarketFactory(factory).treasury()
199
- );
200
+ outAmounts = ISuperComposableYield(SCY).redeemReward(address(this));
200
201
  }
201
202
 
202
- /// the only non-view part in this function is the ISuperComposableYield(SCY).scyIndexCurrent()
203
- function readState() public view returns (MarketParameters memory market) {
203
+ function readState(bool updateRateOracle) public view returns (MarketState memory market) {
204
204
  MarketStorage storage store = marketStorage;
205
205
  market.totalOt = store.totalOt;
206
206
  market.totalScy = store.totalScy;
@@ -216,10 +216,12 @@ contract PendleMarket is PendleBaseToken, IPMarket, ReentrancyGuard {
216
216
  market.lastImpliedRate = store.lastImpliedRate;
217
217
  market.lastTradeTime = store.lastTradeTime;
218
218
 
219
- market.updateNewRateOracle(block.timestamp);
219
+ if (updateRateOracle) {
220
+ market.updateNewRateOracle(block.timestamp);
221
+ }
220
222
  }
221
223
 
222
- function _writeState(MarketParameters memory market) internal {
224
+ function _writeState(MarketState memory market) internal {
223
225
  MarketStorage storage store = marketStorage;
224
226
 
225
227
  store.totalOt = market.totalOt.Int128();
@@ -4,18 +4,19 @@ import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
4
4
  import "../interfaces/IPMarket.sol";
5
5
  import "../interfaces/IPYieldContractFactory.sol";
6
6
  import "../interfaces/IPMarketFactory.sol";
7
- import "../periphery/PermissionsV2.sol";
7
+ import "../periphery/PermissionsV2Upg.sol";
8
8
  import "./PendleMarket.sol";
9
9
 
10
- contract PendleMarketFactory is PermissionsV2, IPMarketFactory {
10
+ contract PendleMarketFactory is PermissionsV2Upg, IPMarketFactory {
11
11
  using EnumerableSet for EnumerableSet.AddressSet;
12
12
 
13
- mapping(address => EnumerableSet.AddressSet) internal otMarkets;
13
+ mapping(address => EnumerableSet.AddressSet) internal markets;
14
+
14
15
  address public immutable yieldContractFactory;
15
16
  address public treasury;
16
17
 
17
18
  constructor(address _governanceManager, address _yieldContractFactory)
18
- PermissionsV2(_governanceManager)
19
+ PermissionsV2Upg(_governanceManager)
19
20
  {
20
21
  yieldContractFactory = _yieldContractFactory;
21
22
  }
@@ -46,7 +47,7 @@ contract PendleMarketFactory is PermissionsV2, IPMarketFactory {
46
47
  reserveFeePercent
47
48
  )
48
49
  );
49
- otMarkets[address(OT)].add(market);
50
+ markets[OT].add(market);
50
51
  }
51
52
 
52
53
  function setTreasury(address newTreasury) external onlyGovernance {
@@ -55,6 +56,6 @@ contract PendleMarketFactory is PermissionsV2, IPMarketFactory {
55
56
 
56
57
  function isValidMarket(address market) external view returns (bool) {
57
58
  address OT = IPMarket(market).OT();
58
- return otMarkets[OT].contains(market);
59
+ return markets[OT].contains(market);
59
60
  }
60
61
  }
@@ -80,7 +80,7 @@ contract PendleBenQiErc20SCY is SCYBaseWithRewards {
80
80
  //////////////////////////////////////////////////////////////*/
81
81
 
82
82
  function scyIndexCurrent() public virtual override returns (uint256 res) {
83
- res = FixedPoint.max(lastScyIndex, IQiToken(qiToken).exchangeRateCurrent());
83
+ res = Math.max(lastScyIndex, IQiToken(qiToken).exchangeRateCurrent());
84
84
  lastScyIndex = res;
85
85
  return res;
86
86
  }
@@ -74,7 +74,7 @@ contract PendleBtrflyScy is SCYBase {
74
74
  //////////////////////////////////////////////////////////////*/
75
75
 
76
76
  function scyIndexCurrent() public virtual override returns (uint256 res) {
77
- res = FixedPoint.max(lastScyIndex, IWXBTRFLY(wxBTRFLY).xBTRFLYValue(FixedPoint.ONE));
77
+ res = Math.max(lastScyIndex, IWXBTRFLY(wxBTRFLY).xBTRFLYValue(Math.ONE));
78
78
  lastScyIndex = res;
79
79
  return res;
80
80
  }
@@ -103,12 +103,7 @@ contract PendleBtrflyScy is SCYBase {
103
103
  //////////////////////////////////////////////////////////////*/
104
104
 
105
105
  //solhint-disable-next-line no-empty-blocks
106
- function redeemReward(address user, address receiver)
107
- public
108
- virtual
109
- override
110
- returns (uint256[] memory)
111
- {}
106
+ function redeemReward(address user) public virtual override returns (uint256[] memory) {}
112
107
 
113
108
  //solhint-disable-next-line no-empty-blocks
114
109
  function updateGlobalReward() public virtual override {}
@@ -66,7 +66,7 @@ contract PendleYearnVaultScy is SCYBase {
66
66
  //////////////////////////////////////////////////////////////*/
67
67
 
68
68
  function scyIndexCurrent() public virtual override returns (uint256 res) {
69
- res = FixedPoint.max(lastScyIndex, IYearnVault(yvToken).pricePerShare());
69
+ res = Math.max(lastScyIndex, IYearnVault(yvToken).pricePerShare());
70
70
  lastScyIndex = res;
71
71
  return res;
72
72
  }
@@ -93,12 +93,7 @@ contract PendleYearnVaultScy is SCYBase {
93
93
  //////////////////////////////////////////////////////////////*/
94
94
 
95
95
  //solhint-disable-next-line no-empty-blocks
96
- function redeemReward(address user, address receiver)
97
- public
98
- virtual
99
- override
100
- returns (uint256[] memory)
101
- {}
96
+ function redeemReward(address user) public virtual override returns (uint256[] memory) {}
102
97
 
103
98
  //solhint-disable-next-line no-empty-blocks
104
99
  function updateGlobalReward() public virtual override {}
@@ -26,13 +26,13 @@ pragma solidity 0.8.9;
26
26
  import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
27
27
  import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
28
28
  import "../libraries/helpers/ExpiryUtilsLib.sol";
29
- import "./misc/BoringOwnable.sol";
29
+ import "../periphery/PermissionsV2Upg.sol";
30
30
  import "../interfaces/IPYieldContractFactory.sol";
31
31
 
32
32
  import "./PendleOwnershipToken.sol";
33
33
  import "./PendleYieldToken.sol";
34
34
 
35
- contract PendleYieldContractFactory is BoringOwnable, IPYieldContractFactory {
35
+ contract PendleYieldContractFactory is PermissionsV2Upg, IPYieldContractFactory {
36
36
  using ExpiryUtils for string;
37
37
 
38
38
  string public constant OT_PREFIX = "OT";
@@ -45,12 +45,15 @@ contract PendleYieldContractFactory is BoringOwnable, IPYieldContractFactory {
45
45
  // SCY => expiry => address
46
46
  mapping(address => mapping(uint256 => address)) public getOT;
47
47
  mapping(address => mapping(uint256 => address)) public getYT;
48
+ mapping(address => bool) public isOT;
49
+ mapping(address => bool) public isYT;
48
50
 
49
51
  constructor(
50
52
  uint256 _expiryDivisor,
51
53
  uint256 _interestFeeRate,
52
- address _treasury
53
- ) BoringOwnable(msg.sender) {
54
+ address _treasury,
55
+ address _governanceManager
56
+ ) PermissionsV2Upg(_governanceManager) {
54
57
  expiryDivisor = _expiryDivisor;
55
58
  interestFeeRate = _interestFeeRate;
56
59
  treasury = _treasury;
@@ -93,17 +96,19 @@ contract PendleYieldContractFactory is BoringOwnable, IPYieldContractFactory {
93
96
 
94
97
  getOT[SCY][expiry] = OT;
95
98
  getYT[SCY][expiry] = YT;
99
+ isOT[OT] = true;
100
+ isYT[YT] = true;
96
101
  }
97
102
 
98
- function setExpiryDivisor(uint256 newExpiryDivisor) external onlyOwner {
103
+ function setExpiryDivisor(uint256 newExpiryDivisor) external onlyGovernance {
99
104
  expiryDivisor = newExpiryDivisor;
100
105
  }
101
106
 
102
- function setInterestFeeRate(uint256 newInterestFeeRate) external onlyOwner {
107
+ function setInterestFeeRate(uint256 newInterestFeeRate) external onlyGovernance {
103
108
  interestFeeRate = newInterestFeeRate;
104
109
  }
105
110
 
106
- function setTreasury(address newTreasury) external onlyOwner {
111
+ function setTreasury(address newTreasury) external onlyGovernance {
107
112
  treasury = newTreasury;
108
113
  }
109
114
  }
@@ -5,15 +5,22 @@ import "./PendleBaseToken.sol";
5
5
  import "../SuperComposableYield/ISuperComposableYield.sol";
6
6
  import "../interfaces/IPYieldToken.sol";
7
7
  import "../interfaces/IPOwnershipToken.sol";
8
- import "../libraries/math/FixedPoint.sol";
8
+ import "../libraries/math/Math.sol";
9
9
  import "../interfaces/IPYieldContractFactory.sol";
10
- import "@openzeppelin/contracts/utils/math/Math.sol";
11
10
  import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
12
11
  import "../SuperComposableYield/SCYUtils.sol";
13
12
  import "../SuperComposableYield/implementations/RewardManager.sol";
14
13
 
14
+ /*
15
+ With YT yielding more SCYs overtime, which is allowed to be redeemed by users, the reward distribution should
16
+ be based on the amount of SCYs that their YT currently represent, plus with their dueInterest.
17
+
18
+ It has been proven and tested that impliedScyBalance will not change over time, unless users redeem their interest or redeemYO.
19
+
20
+ Due to this, it is required to update users' accruedReward STRICTLY BEFORE redeeming their interest.
21
+ */
15
22
  contract PendleYieldToken is PendleBaseToken, IPYieldToken, RewardManager {
16
- using FixedPoint for uint256;
23
+ using Math for uint256;
17
24
  using SafeERC20 for IERC20;
18
25
 
19
26
  struct UserData {
@@ -77,39 +84,58 @@ contract PendleYieldToken is PendleBaseToken, IPYieldToken, RewardManager {
77
84
  _afterTransferOutSCY();
78
85
  }
79
86
 
87
+ function redeemDueInterestAndRewards(address user)
88
+ public
89
+ returns (uint256 interestOut, uint256[] memory rewardsOut)
90
+ {
91
+ // redeemDueRewards before redeemDueInterest
92
+ updateUserReward(user);
93
+ updateUserInterest(user);
94
+ rewardsOut = _doTransferOutRewardsForUser(user, user);
95
+ interestOut = _doTransferOutDueInterest(user);
96
+ }
97
+
80
98
  function redeemDueInterest(address user) public returns (uint256 interestOut) {
81
- _updateDueInterest(user);
99
+ updateUserReward(user); /// strictly required, see above for explanation
100
+ updateUserInterest(user);
101
+ interestOut = _doTransferOutDueInterest(user);
102
+ }
82
103
 
83
- uint256 interestPreFee = data[user].dueInterest;
84
- data[user].dueInterest = 0;
104
+ function redeemDueRewards(address user) public returns (uint256[] memory rewardsOut) {
105
+ updateUserReward(user);
106
+ rewardsOut = _doTransferOutRewardsForUser(user, user);
107
+ }
85
108
 
86
- uint256 feeRate = IPYieldContractFactory(factory).interestFeeRate();
87
- uint256 feeAmount = interestPreFee.mulDown(feeRate);
88
- totalProtocolFee += feeAmount;
109
+ function updateGlobalReward() external {
110
+ address[] memory rewardTokens = getRewardTokens();
111
+ _updateGlobalReward(rewardTokens, IERC20(SCY).balanceOf(address(this)));
112
+ }
89
113
 
90
- interestOut = interestPreFee - feeAmount;
91
- IERC20(SCY).safeTransfer(user, interestOut);
92
- _afterTransferOutSCY();
114
+ function updateUserReward(address user) public {
115
+ uint256 impliedScyBalance = getImpliedScyBalance(user);
116
+ uint256 totalScy = IERC20(SCY).balanceOf(address(this));
117
+ _updateUserReward(user, impliedScyBalance, totalScy);
93
118
  }
94
119
 
95
- function redeemDueRewards(address user, address receiver)
96
- public
97
- returns (uint256[] memory rewardsOut)
98
- {
99
- if (user != msg.sender) require(receiver == user, "invalid receiver");
100
- else require(receiver != address(0), "zero address");
120
+ function updateUserInterest(address user) public {
121
+ uint256 prevIndex = data[user].lastScyIndex;
101
122
 
102
- _updateUserReward(user, balanceOf(user), totalSupply());
103
- rewardsOut = _doTransferOutRewardsForUser(user, receiver);
104
- }
123
+ uint256 currentIndex = getScyIndexBeforeExpiry();
105
124
 
106
- function updateGlobalReward() public virtual {
107
- address[] memory rewardTokens = getRewardTokens();
108
- _updateGlobalReward(rewardTokens, totalSupply());
109
- }
125
+ if (prevIndex == currentIndex) return;
126
+ if (prevIndex == 0) {
127
+ data[user].lastScyIndex = currentIndex;
128
+ return;
129
+ }
130
+
131
+ uint256 principal = balanceOf(user);
132
+
133
+ uint256 interestFromYT = (principal * (currentIndex - prevIndex)).divDown(
134
+ prevIndex * currentIndex
135
+ );
110
136
 
111
- function updateUserReward(address user) public virtual {
112
- _updateUserReward(user, balanceOf(user), totalSupply());
137
+ data[user].dueInterest += interestFromYT;
138
+ data[user].lastScyIndex = currentIndex;
113
139
  }
114
140
 
115
141
  function _updateGlobalReward(address[] memory rewardTokens, uint256 totalSupply)
@@ -138,8 +164,21 @@ contract PendleYieldToken is PendleBaseToken, IPYieldToken, RewardManager {
138
164
  }
139
165
  }
140
166
 
167
+ function _doTransferOutDueInterest(address user) internal returns (uint256 interestOut) {
168
+ uint256 interestPreFee = data[user].dueInterest;
169
+ data[user].dueInterest = 0;
170
+
171
+ uint256 feeRate = IPYieldContractFactory(factory).interestFeeRate();
172
+ uint256 feeAmount = interestPreFee.mulDown(feeRate);
173
+ totalProtocolFee += feeAmount;
174
+
175
+ interestOut = interestPreFee - feeAmount;
176
+ IERC20(SCY).safeTransfer(user, interestOut);
177
+ _afterTransferOutSCY();
178
+ }
179
+
141
180
  function _redeemExternalReward() internal virtual override {
142
- ISuperComposableYield(SCY).redeemReward(address(this), address(this));
181
+ ISuperComposableYield(SCY).redeemReward(address(this));
143
182
  }
144
183
 
145
184
  function getRewardTokens()
@@ -158,8 +197,19 @@ contract PendleYieldToken is PendleBaseToken, IPYieldToken, RewardManager {
158
197
  lastScyIndexBeforeExpiry = res;
159
198
  }
160
199
 
200
+ /**
201
+ * In case the pool is expired and there is left some SCY not yet redeemed from the contract, the rewards should
202
+ * be claimed before withdrawing to treasury.
203
+ *
204
+ * And since the reward distribution (which based on users' dueInterest) stopped at the scyIndexBeforeExpiry, it is not
205
+ * necessary to updateDueInterest along with reward here.
206
+ */
161
207
  function withdrawFeeToTreasury() public {
162
- address[] memory rewardTokens = ISuperComposableYield(SCY).getRewardTokens();
208
+ address[] memory rewardTokens = getRewardTokens();
209
+ if (isExpired()) {
210
+ _updateGlobalReward(rewardTokens, IERC20(SCY).balanceOf(address(this))); // as refered to the doc above
211
+ }
212
+
163
213
  uint256 length = rewardTokens.length;
164
214
  address treasury = IPYieldContractFactory(factory).treasury();
165
215
 
@@ -177,27 +227,6 @@ contract PendleYieldToken is PendleBaseToken, IPYieldToken, RewardManager {
177
227
  }
178
228
  }
179
229
 
180
- function _updateDueInterest(address user) internal {
181
- uint256 prevIndex = data[user].lastScyIndex;
182
-
183
- uint256 currentIndex = getScyIndexBeforeExpiry();
184
-
185
- if (prevIndex == currentIndex) return;
186
- if (prevIndex == 0) {
187
- data[user].lastScyIndex = currentIndex;
188
- return;
189
- }
190
-
191
- uint256 principal = balanceOf(user);
192
-
193
- uint256 interestFromYT = (principal * (currentIndex - prevIndex)).divDown(
194
- prevIndex * currentIndex
195
- );
196
-
197
- data[user].dueInterest += interestFromYT;
198
- data[user].lastScyIndex = currentIndex;
199
- }
200
-
201
230
  function _calcAmountToMint(uint256 amount) internal returns (uint256) {
202
231
  return SCYUtils.scyToAsset(getScyIndexBeforeExpiry(), amount);
203
232
  }
@@ -217,20 +246,29 @@ contract PendleYieldToken is PendleBaseToken, IPYieldToken, RewardManager {
217
246
  lastScyBalance = IERC20(SCY).balanceOf(address(this));
218
247
  }
219
248
 
249
+ function getImpliedScyBalance(address user) public view returns (uint256) {
250
+ uint256 scyIndex = data[user].lastScyIndex;
251
+ if (scyIndex == 0) return 0;
252
+ return SCYUtils.assetToScy(scyIndex, balanceOf(user)) + data[user].dueInterest;
253
+ }
254
+
220
255
  function _beforeTokenTransfer(
221
256
  address from,
222
257
  address to,
223
258
  uint256
224
259
  ) internal override {
225
260
  address[] memory rewardTokens = getRewardTokens();
226
- _updateGlobalReward(rewardTokens, totalSupply());
261
+ _updateGlobalReward(rewardTokens, IERC20(SCY).balanceOf(address(this)));
262
+
263
+ // Before the change in YT balance, users' impliedScyBalance is kept unchanged from last time
264
+ // Therefore, both updating due interest before or after due reward work the same.
227
265
  if (from != address(0) && from != address(this)) {
228
- _updateDueInterest(from);
229
- _updateUserRewardSkipGlobal(rewardTokens, from, balanceOf(from));
266
+ updateUserInterest(from);
267
+ _updateUserRewardSkipGlobal(rewardTokens, from, getImpliedScyBalance(from));
230
268
  }
231
269
  if (to != address(0) && to != address(this)) {
232
- _updateDueInterest(to);
233
- _updateUserRewardSkipGlobal(rewardTokens, to, balanceOf(to));
270
+ updateUserInterest(to);
271
+ _updateUserRewardSkipGlobal(rewardTokens, to, getImpliedScyBalance(to));
234
272
  }
235
273
  }
236
274
  }