-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathPrincipalTokenUtil.sol
208 lines (196 loc) · 8.24 KB
/
PrincipalTokenUtil.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.20;
import "../interfaces/IYieldToken.sol";
import "../interfaces/IPrincipalToken.sol";
import "../interfaces/IRegistry.sol";
import "openzeppelin-contracts/interfaces/IERC4626.sol";
import "openzeppelin-math/Math.sol";
import "../libraries/RayMath.sol";
/**
* @title PrincipalTokenUtil library
* @author Spectra Finance
* @notice Provides miscellaneous utils for computations in PrincipalToken.sol.
*/
library PrincipalTokenUtil {
using Math for uint256;
using RayMath for uint256;
error AssetDoesNotImplementMetadata();
uint256 private constant SAFETY_BOUND = 100; // used to favour the protocol in case of approximations
uint256 private constant FEE_DIVISOR = 1e18; // equivalent to 100% fees
uint256 private constant MIN_LENGTH = 32; // minimum length the encoded decimals should have
/**
* @dev Computes the yield for a specified user since the last update.
* @param _user The address for which the yield is to be calculated.
* @param _currentYieldInIBT the current yield of user in IBT
* @param _oldIBTRate the previous deposit IBT rate of user (in Ray)
* @param _ibtRate the current IBT rate (in Ray)
* @param _oldPTRate the previous deposit pt rate of user (in Ray)
* @param _ptRate the current PT rate (in Ray)
* @param _yt the address of YT
* @return updatedYieldInIBT the calculated yield in IBT of user
*/
function computeYield(
address _user,
uint256 _currentYieldInIBT,
uint256 _oldIBTRate,
uint256 _ibtRate,
uint256 _oldPTRate,
uint256 _ptRate,
address _yt
) external view returns (uint256 updatedYieldInIBT) {
if (_oldPTRate == _ptRate && _ibtRate == _oldIBTRate) {
return _currentYieldInIBT;
}
uint8 ytDecimals = IERC20Metadata(_yt).decimals();
uint256 newYieldInIBTRay;
uint256 userYTBalanceInRay = IYieldToken(_yt).actualBalanceOf(_user).toRay(ytDecimals);
// ibtOfPT is the yield generated by each PT corresponding to the YTs that the user holds
uint256 ibtOfPTInRay = userYTBalanceInRay.mulDiv(_oldPTRate, _oldIBTRate);
if (_oldPTRate == _ptRate && _ibtRate > _oldIBTRate) {
// only positive yield happened
newYieldInIBTRay = ibtOfPTInRay.mulDiv(_ibtRate - _oldIBTRate, _ibtRate);
} else {
if (_oldPTRate > _ptRate) {
// PT depeg happened
uint256 yieldInAssetRay;
uint256 actualNegativeYieldInAssetRay = _convertToAssetsWithRate(
userYTBalanceInRay,
_oldPTRate - _ptRate,
Math.Rounding.Floor
);
if (_ibtRate >= _oldIBTRate) {
// both negative and positive yield happened, more positive
yieldInAssetRay =
actualNegativeYieldInAssetRay +
_convertToAssetsWithRate(
ibtOfPTInRay,
_ibtRate - _oldIBTRate,
Math.Rounding.Floor
);
} else {
// either both negative and positive yield happened, more negative
// or only negative yield happened
uint256 expectedNegativeYieldInAssetRay = _convertToAssetsWithRate(
ibtOfPTInRay,
(_oldIBTRate - _ibtRate),
Math.Rounding.Ceil
);
yieldInAssetRay = expectedNegativeYieldInAssetRay >
actualNegativeYieldInAssetRay
? 0
: actualNegativeYieldInAssetRay - expectedNegativeYieldInAssetRay;
yieldInAssetRay = yieldInAssetRay.fromRay(
IERC20Metadata(IPrincipalToken(IYieldToken(_yt).getPT()).underlying())
.decimals()
) < SAFETY_BOUND
? 0
: yieldInAssetRay;
}
newYieldInIBTRay = _convertToSharesWithRate(
yieldInAssetRay,
_ibtRate,
Math.Rounding.Floor
);
} else {
// PT rate increased or did not depeg on IBT rate decrease
revert IPrincipalToken.RateError();
}
}
updatedYieldInIBT = _currentYieldInIBT + newYieldInIBTRay.fromRay(ytDecimals);
}
/**
* @dev Attempts to fetch the token decimals. Reverts if the attempt failed in some way.
* @param _token The token address
* @return The ERC20 token decimals
*/
function tryGetTokenDecimals(address _token) external view returns (uint8) {
(bool success, bytes memory encodedDecimals) = _token.staticcall(
abi.encodeCall(IERC20Metadata.decimals, ())
);
if (success && encodedDecimals.length >= MIN_LENGTH) {
uint256 returnedDecimals = abi.decode(encodedDecimals, (uint256));
if (returnedDecimals <= type(uint8).max) {
return uint8(returnedDecimals);
}
}
revert AssetDoesNotImplementMetadata();
}
/**
* @dev Convert underlying amount to share, with the given rate
* @param _assetsInRay The amount of underlying (in Ray)
* @param _rate The share price in underlying (in Ray)
* @param _rounding The rounding type to be used in the computation
* @return sharesInRay The amount of share (in Ray)
*/
function _convertToSharesWithRate(
uint256 _assetsInRay,
uint256 _rate,
Math.Rounding _rounding
) internal pure returns (uint256 sharesInRay) {
if (_rate == 0) {
revert IPrincipalToken.RateError();
}
sharesInRay = _assetsInRay.mulDiv(RayMath.RAY_UNIT, _rate, _rounding);
}
/**
* @dev Convert share amount to underlying, with the given rate
* @param _sharesInRay The amount of share (in Ray)
* @param _rate The share price in underlying (in Ray)
* @param _rounding The rounding type to be used in the computation
* @return assetsInRay The amount of underlying (in Ray)
*/
function _convertToAssetsWithRate(
uint256 _sharesInRay,
uint256 _rate,
Math.Rounding _rounding
) internal pure returns (uint256 assetsInRay) {
assetsInRay = _sharesInRay.mulDiv(_rate, RayMath.RAY_UNIT, _rounding);
}
/**
* @dev Compute tokenization fee for a given amount
* @param _amount The amount to tokenize
* @param _pt The address of the pt on which the fee is being paid
* @param _registry The address of registry that stores fee rate
* @return returns The calculated tokenization fee
*/
function _computeTokenizationFee(
uint256 _amount,
address _pt,
address _registry
) internal view returns (uint256) {
return
_amount
.mulDiv(IRegistry(_registry).getTokenizationFee(), FEE_DIVISOR, Math.Rounding.Ceil)
.mulDiv(
FEE_DIVISOR - IRegistry(_registry).getFeeReduction(_pt, msg.sender),
FEE_DIVISOR,
Math.Rounding.Ceil
);
}
/**
* @dev Compute yield fee for a given amount
* @param _amount the amount of yield
* @param _registry the address of registry that stores fee rate
* @return returns the calculated yield fee
*/
function _computeYieldFee(uint256 _amount, address _registry) internal view returns (uint256) {
return _amount.mulDiv(IRegistry(_registry).getYieldFee(), FEE_DIVISOR, Math.Rounding.Ceil);
}
/**
* @dev Compute flashloan fee for a given amount
* @param _amount the amount to flashloan
* @param _registry the address of registry that stores fee rate
* @return returns the calculated flashloan fee
*/
function _computeFlashloanFee(
uint256 _amount,
address _registry
) internal view returns (uint256) {
return
_amount.mulDiv(
IRegistry(_registry).getPTFlashLoanFee(),
FEE_DIVISOR,
Math.Rounding.Ceil
);
}
}