@pendle/core-v2 0.17.6-testnet → 0.17.7-testnet
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.
- package/contracts/core/StandardizedYield/SYBase.sol +5 -23
- package/contracts/core/StandardizedYield/SYBaseWithRewards.sol +1 -3
- package/contracts/core/StandardizedYield/implementations/Ape/PendleApeStakingSY.sol +184 -0
- package/contracts/core/libraries/Errors.sol +2 -0
- package/contracts/interfaces/IApeStaking.sol +27 -0
- package/deployments/43113-markets/APE-30-MARCH.json +10 -0
- package/package.json +1 -1
|
@@ -1,22 +1,13 @@
|
|
|
1
1
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
2
2
|
pragma solidity 0.8.17;
|
|
3
|
-
import "../../interfaces/IStandardizedYield.sol";
|
|
4
3
|
|
|
4
|
+
import "../../interfaces/IStandardizedYield.sol";
|
|
5
5
|
import "../erc20/PendleERC20Permit.sol";
|
|
6
|
-
|
|
7
6
|
import "../libraries/math/Math.sol";
|
|
8
7
|
import "../libraries/TokenHelper.sol";
|
|
9
8
|
import "../libraries/Errors.sol";
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
abstract contract SYBase is
|
|
14
|
-
IStandardizedYield,
|
|
15
|
-
PendleERC20Permit,
|
|
16
|
-
TokenHelper,
|
|
17
|
-
BoringOwnableUpgradeable,
|
|
18
|
-
Pausable
|
|
19
|
-
{
|
|
9
|
+
|
|
10
|
+
abstract contract SYBase is IStandardizedYield, PendleERC20Permit, TokenHelper {
|
|
20
11
|
using Math for uint256;
|
|
21
12
|
|
|
22
13
|
address public immutable yieldToken;
|
|
@@ -25,9 +16,8 @@ abstract contract SYBase is
|
|
|
25
16
|
string memory _name,
|
|
26
17
|
string memory _symbol,
|
|
27
18
|
address _yieldToken
|
|
28
|
-
) PendleERC20Permit(_name, _symbol, IERC20Metadata(_yieldToken).decimals())
|
|
19
|
+
) PendleERC20Permit(_name, _symbol, IERC20Metadata(_yieldToken).decimals()) {
|
|
29
20
|
yieldToken = _yieldToken;
|
|
30
|
-
__BoringOwnable_init();
|
|
31
21
|
}
|
|
32
22
|
|
|
33
23
|
// solhint-disable no-empty-blocks
|
|
@@ -189,19 +179,11 @@ abstract contract SYBase is
|
|
|
189
179
|
return _previewRedeem(tokenOut, amountSharesToRedeem);
|
|
190
180
|
}
|
|
191
181
|
|
|
192
|
-
function pause() external onlyOwner {
|
|
193
|
-
_pause();
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
function unpause() external onlyOwner {
|
|
197
|
-
_unpause();
|
|
198
|
-
}
|
|
199
|
-
|
|
200
182
|
function _beforeTokenTransfer(
|
|
201
183
|
address,
|
|
202
184
|
address,
|
|
203
185
|
uint256
|
|
204
|
-
) internal virtual override
|
|
186
|
+
) internal virtual override {}
|
|
205
187
|
|
|
206
188
|
function _previewDeposit(address tokenIn, uint256 amountTokenToDeposit)
|
|
207
189
|
internal
|
|
@@ -29,7 +29,6 @@ abstract contract SYBaseWithRewards is SYBase, RewardManager {
|
|
|
29
29
|
virtual
|
|
30
30
|
override
|
|
31
31
|
nonReentrant
|
|
32
|
-
whenNotPaused
|
|
33
32
|
returns (uint256[] memory rewardAmounts)
|
|
34
33
|
{
|
|
35
34
|
_updateAndDistributeRewards(user);
|
|
@@ -74,7 +73,6 @@ abstract contract SYBaseWithRewards is SYBase, RewardManager {
|
|
|
74
73
|
function rewardIndexesCurrent()
|
|
75
74
|
external
|
|
76
75
|
override
|
|
77
|
-
whenNotPaused
|
|
78
76
|
nonReentrant
|
|
79
77
|
returns (uint256[] memory indexes)
|
|
80
78
|
{
|
|
@@ -122,7 +120,7 @@ abstract contract SYBaseWithRewards is SYBase, RewardManager {
|
|
|
122
120
|
address from,
|
|
123
121
|
address to,
|
|
124
122
|
uint256
|
|
125
|
-
) internal virtual override
|
|
123
|
+
) internal virtual override {
|
|
126
124
|
_updateAndDistributeRewardsForTwo(from, to);
|
|
127
125
|
}
|
|
128
126
|
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
2
|
+
pragma solidity 0.8.17;
|
|
3
|
+
|
|
4
|
+
import "../../SYBase.sol";
|
|
5
|
+
import "../../../../interfaces/IApeStaking.sol";
|
|
6
|
+
|
|
7
|
+
contract PendleApeStakingSY is SYBase {
|
|
8
|
+
using Math for uint256;
|
|
9
|
+
|
|
10
|
+
uint256 public constant APE_COIN_POOL_ID = 0;
|
|
11
|
+
uint256 public constant MIN_APE_DEPOSIT = 10**18;
|
|
12
|
+
uint256 public constant EPOCH_LENGTH = 1 hours;
|
|
13
|
+
|
|
14
|
+
address public immutable apeStaking;
|
|
15
|
+
address public immutable apeCoin;
|
|
16
|
+
|
|
17
|
+
uint256 private lastRewardClaimedEpoch;
|
|
18
|
+
|
|
19
|
+
constructor(
|
|
20
|
+
string memory _name,
|
|
21
|
+
string memory _symbol,
|
|
22
|
+
address _apeCoin,
|
|
23
|
+
address _apeStaking
|
|
24
|
+
)
|
|
25
|
+
SYBase(_name, _symbol, _apeCoin)
|
|
26
|
+
{
|
|
27
|
+
apeStaking = _apeStaking;
|
|
28
|
+
apeCoin = _apeCoin;
|
|
29
|
+
lastRewardClaimedEpoch = _getCurrentEpochId();
|
|
30
|
+
_safeApproveInf(apeCoin, apeStaking);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function _deposit(address, uint256 amountDeposited)
|
|
34
|
+
internal
|
|
35
|
+
virtual
|
|
36
|
+
override
|
|
37
|
+
returns (uint256 amountSharesOut)
|
|
38
|
+
{
|
|
39
|
+
// Respecting APE's deposit invariant & prevent frontrunning on deployment
|
|
40
|
+
if (amountDeposited < MIN_APE_DEPOSIT) {
|
|
41
|
+
revert Errors.SYApeDepositAmountTooSmall(amountDeposited);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
_harvestAndCompound();
|
|
45
|
+
|
|
46
|
+
// As SY Base is pulling the tokenIn first, the totalAsset should exclude user's deposit
|
|
47
|
+
uint256 priorTotalAssetOwned = getTotalAssetOwned() - amountDeposited;
|
|
48
|
+
|
|
49
|
+
if (totalSupply() == 0) {
|
|
50
|
+
amountSharesOut = amountDeposited;
|
|
51
|
+
} else {
|
|
52
|
+
// The upcoming calculation can be reduced to amountDeposited.divDown(exchangeRate())
|
|
53
|
+
// The following calculation is choosen instead to minimize precision error
|
|
54
|
+
amountSharesOut = (amountDeposited * totalSupply()) / priorTotalAssetOwned;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function _redeem(
|
|
59
|
+
address receiver,
|
|
60
|
+
address,
|
|
61
|
+
uint256 amountSharesToRedeem
|
|
62
|
+
) internal virtual override returns (uint256 amountTokenOut) {
|
|
63
|
+
_harvestAndCompound();
|
|
64
|
+
|
|
65
|
+
// As SY is burned before calling _redeem(), we should account for priorSupply
|
|
66
|
+
uint256 priorTotalSupply = totalSupply() + amountSharesToRedeem;
|
|
67
|
+
|
|
68
|
+
if (amountSharesToRedeem == priorTotalSupply) {
|
|
69
|
+
amountTokenOut = getTotalAssetOwned();
|
|
70
|
+
} else {
|
|
71
|
+
// The upcoming calculation can be reduced to amountSharesToRedeem.mulDown(exchangeRate())
|
|
72
|
+
// The following calculation is choosen instead to minimize precision error
|
|
73
|
+
amountTokenOut = (amountSharesToRedeem * getTotalAssetOwned()) / priorTotalSupply;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// There might be case when the contract is holding < 1 APE reward and user is withdrawing everything out of it
|
|
77
|
+
if (amountTokenOut > _selfBalance(apeCoin)) {
|
|
78
|
+
IApeStaking(apeStaking).withdrawApeCoin(
|
|
79
|
+
amountTokenOut - _selfBalance(apeCoin),
|
|
80
|
+
address(this)
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
_transferOut(apeCoin, receiver, amountTokenOut);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function exchangeRate() public view virtual override returns (uint256) {
|
|
87
|
+
return getTotalAssetOwned().divDown(totalSupply());
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/*///////////////////////////////////////////////////////////////
|
|
91
|
+
AUTOCOMPOUND FEATURE
|
|
92
|
+
//////////////////////////////////////////////////////////////*/
|
|
93
|
+
|
|
94
|
+
function harvestAndCompound() external {
|
|
95
|
+
_harvestAndCompound();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function getTotalAssetOwned() public view returns (uint256 totalAssetOwned) {
|
|
99
|
+
(uint256 stakedAmount, ) = IApeStaking(apeStaking).addressPosition(address(this));
|
|
100
|
+
uint256 unclaimedAmount = IApeStaking(apeStaking).pendingRewards(
|
|
101
|
+
APE_COIN_POOL_ID,
|
|
102
|
+
address(this),
|
|
103
|
+
0
|
|
104
|
+
);
|
|
105
|
+
uint256 floatingAmount = _selfBalance(apeCoin);
|
|
106
|
+
totalAssetOwned = stakedAmount + unclaimedAmount + floatingAmount;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function _harvestAndCompound() internal {
|
|
110
|
+
// Claim reward
|
|
111
|
+
uint256 currentEpochId = _getCurrentEpochId();
|
|
112
|
+
if (currentEpochId != lastRewardClaimedEpoch) {
|
|
113
|
+
IApeStaking(apeStaking).claimSelfApeCoin();
|
|
114
|
+
lastRewardClaimedEpoch = currentEpochId;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Deposit APE
|
|
118
|
+
uint256 amountAssetToCompound = _selfBalance(apeCoin);
|
|
119
|
+
if (amountAssetToCompound >= MIN_APE_DEPOSIT) {
|
|
120
|
+
IApeStaking(apeStaking).depositSelfApeCoin(amountAssetToCompound);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/*///////////////////////////////////////////////////////////////
|
|
125
|
+
MISC FUNCTIONS FOR METADATA
|
|
126
|
+
//////////////////////////////////////////////////////////////*/
|
|
127
|
+
|
|
128
|
+
function _previewDeposit(address, uint256 amountTokenToDeposit)
|
|
129
|
+
internal
|
|
130
|
+
view
|
|
131
|
+
override
|
|
132
|
+
returns (uint256 amountSharesOut)
|
|
133
|
+
{
|
|
134
|
+
if (totalSupply() == 0) {
|
|
135
|
+
amountSharesOut = amountTokenToDeposit;
|
|
136
|
+
} else {
|
|
137
|
+
amountSharesOut = (amountTokenToDeposit * totalSupply()) / getTotalAssetOwned();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function _previewRedeem(address, uint256 amountSharesToRedeem)
|
|
142
|
+
internal
|
|
143
|
+
view
|
|
144
|
+
override
|
|
145
|
+
returns (uint256 amountTokenOut)
|
|
146
|
+
{
|
|
147
|
+
amountTokenOut = (amountSharesToRedeem * getTotalAssetOwned()) / totalSupply();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
function _getCurrentEpochId() private view returns (uint256) {
|
|
152
|
+
return block.timestamp / EPOCH_LENGTH;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function getTokensIn() public view virtual override returns (address[] memory res) {
|
|
156
|
+
res = new address[](1);
|
|
157
|
+
res[0] = apeCoin;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function getTokensOut() public view virtual override returns (address[] memory res) {
|
|
161
|
+
res = new address[](1);
|
|
162
|
+
res[0] = apeCoin;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function isValidTokenIn(address token) public view virtual override returns (bool) {
|
|
166
|
+
return token == apeCoin;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function isValidTokenOut(address token) public view virtual override returns (bool) {
|
|
170
|
+
return token == apeCoin;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function assetInfo()
|
|
174
|
+
external
|
|
175
|
+
view
|
|
176
|
+
returns (
|
|
177
|
+
AssetType assetType,
|
|
178
|
+
address assetAddress,
|
|
179
|
+
uint8 assetDecimals
|
|
180
|
+
)
|
|
181
|
+
{
|
|
182
|
+
return (AssetType.TOKEN, apeCoin, IERC20Metadata(apeCoin).decimals());
|
|
183
|
+
}
|
|
184
|
+
}
|
|
@@ -111,6 +111,8 @@ library Errors {
|
|
|
111
111
|
error SYCurveInvalidPid();
|
|
112
112
|
error SYCurve3crvPoolNotFound();
|
|
113
113
|
|
|
114
|
+
error SYApeDepositAmountTooSmall(uint256 amountDeposited);
|
|
115
|
+
|
|
114
116
|
// Liquidity Mining
|
|
115
117
|
error VCInactivePool(address pool);
|
|
116
118
|
error VCPoolAlreadyActive(address pool);
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
2
|
+
|
|
3
|
+
pragma solidity 0.8.17;
|
|
4
|
+
|
|
5
|
+
interface IApeStaking {
|
|
6
|
+
function apeCoin() external view returns (address);
|
|
7
|
+
|
|
8
|
+
function depositSelfApeCoin(uint256 _amount) external;
|
|
9
|
+
|
|
10
|
+
function withdrawApeCoin(uint256 _amount, address _recipient) external;
|
|
11
|
+
|
|
12
|
+
function claimSelfApeCoin() external;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @notice when poolId is set to 0, tokenId's value does not matter
|
|
16
|
+
*/
|
|
17
|
+
function pendingRewards(
|
|
18
|
+
uint256 _poolId,
|
|
19
|
+
address _address,
|
|
20
|
+
uint256 _tokenId
|
|
21
|
+
) external view returns (uint256);
|
|
22
|
+
|
|
23
|
+
function addressPosition(address addr)
|
|
24
|
+
external
|
|
25
|
+
view
|
|
26
|
+
returns (uint256 stakedAmount, int256 rewardsDebt);
|
|
27
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "sAPE",
|
|
3
|
+
"SY": "0xDE080bEeB90e62F61936C113Fb46E62aa766cF1f",
|
|
4
|
+
"YT": "0x6537939Ec3e9da193D234A36bC5664B3cA570bc8",
|
|
5
|
+
"PT": "0x1432364a6a56d30810087fbD2ed197e609451299",
|
|
6
|
+
"market": "0x94B81B4cF70849E994f17b872C0e8E448443aE9b",
|
|
7
|
+
"expiry": 1680134400,
|
|
8
|
+
"scalarRoot": "2200000000000000000",
|
|
9
|
+
"initAnchor": "1151500000000000000"
|
|
10
|
+
}
|
package/package.json
CHANGED