-
Notifications
You must be signed in to change notification settings - Fork 6
/
ThreePieceWiseLinearPriceCurve.sol
165 lines (142 loc) · 6.97 KB
/
ThreePieceWiseLinearPriceCurve.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
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.6.11;
import "../Interfaces/IPriceCurve.sol";
import "../Dependencies/SafeMath.sol";
import "../Dependencies/Ownable.sol";
/**
* This contract is used to calculate the variable fee for an input of tokens.
* Uses three linear piecewise functions to calculate the fee, and the average
* of the system collateralization by that asset before and after the tx.
*/
contract ThreePieceWiseLinearPriceCurve is IPriceCurve, Ownable {
using SafeMath for uint256;
string name;
uint256 m1;
uint256 b1;
uint256 cutoff1;
uint256 m2;
uint256 b2;
uint256 cutoff2;
uint256 m3;
uint256 b3;
uint256 decayTime;
uint lastFeeTime;
uint lastFeePercent;
uint dollarCap;
address whitelistAddress;
bool private addressesSet;
/**
* f1 = m1 * x + b1
* f1 meets f2 at cutoff1, which is defined by that intersection point and slope m2
* f2 meets f3 at cutoff2, which is defined by that intersection point and slope m3
* Everything in terms of actual * 1e18, scaled by 1e18 because can't do percentages
* Decimal precision = 1e18
*/
/**
* Function for setting slopes and intercepts of linear functions used for fee calculations.
*/
function adjustParams(string memory _name, uint256 _m1, uint256 _b1, uint256 _m2, uint256 _cutoff1, uint256 _m3, uint256 _cutoff2, uint _dollarCap) public onlyOwner {
require(_cutoff1 <= _cutoff2, "Cutoffs must be increasing");
name = _name;
m1 = _m1;
b1 = _b1;
m2 = _m2;
b2 = _m1.mul(_cutoff1).div(1e18).add(_b1).sub(_m2.mul(_cutoff1).div(1e18));
cutoff1 = _cutoff1;
m3 = _m3;
b3 = _m2.mul(_cutoff2).div(1e18).add(b2).sub(_m3.mul(_cutoff2).div(1e18));
cutoff2 = _cutoff2;
dollarCap = _dollarCap; // Cap in VC terms of max of this asset. dollarCap = 0 means no cap. No cap.
decayTime = 5 days;
}
// Set the whitelist address so that the fee can only be updated by whitelistAddress
function setAddresses(address _whitelistAddress) public override onlyOwner {
require(!addressesSet, "addresses already set");
whitelistAddress = _whitelistAddress;
addressesSet = true;
}
// Set the decay time in seconds
function setDecayTime(uint _decayTime) public override onlyOwner {
decayTime = _decayTime;
}
// Gets the fee cap and time currently. Used for setting new values for next price curve.
function getFeeCapAndTime() external override view returns (uint256 _lastFeePercent, uint256 _lastFeeTime) {
return (lastFeePercent, lastFeeTime);
}
// Function for setting the old price curve's last fee cap / value to the new fee cap / value.
// Called only by whitelist.
function setFeeCapAndTime(uint256 _lastFeePercent, uint256 _lastFeeTime) external override {
require(msg.sender == whitelistAddress, "setFeeCapAndTime: not a whitelisted address");
lastFeePercent = _lastFeePercent;
lastFeeTime = _lastFeeTime;
}
/**
* Function for getting the fee for a particular collateral type based on percent of YUSD backed
* by this asset.
* @param _collateralVCInput is how much collateral is being input by the user into the system
* @param _totalCollateralVCBalance is how much collateral is in the system
* @param _totalVCBalancePost is how much VC the system for all collaterals after all adjustments (additions, subtractions)
*/
function getFee(uint256 _collateralVCInput, uint256 _totalCollateralVCBalance, uint256 _totalVCBalancePre, uint256 _totalVCBalancePost) override external view returns (uint256 fee) {
// If dollarCap == 0, then it is not capped. Otherwise, then the total + the total input must be less than the cap.
if (dollarCap != 0) {
require(_totalCollateralVCBalance.add(_collateralVCInput) <= dollarCap, "Collateral input exceeds cap");
}
uint feePre = _getFeePoint(_totalCollateralVCBalance, _totalVCBalancePre);
uint feePost = _getFeePoint(_totalCollateralVCBalance.add(_collateralVCInput), _totalVCBalancePost);
uint decayedLastFee = calculateDecayedFee();
uint feeCalculated = _max((feePre.add(feePost)).div(2), decayedLastFee);
return feeCalculated;
}
// Called only by whitelist. Updates the last fee time and last fee percent
function getFeeAndUpdate(uint256 _collateralVCInput, uint256 _totalCollateralVCBalance, uint256 _totalVCBalancePre, uint256 _totalVCBalancePost) override external returns (uint256 fee) {
require(msg.sender == whitelistAddress, "Only whitelist can update fee");
// If dollarCap == 0, then it is not capped. Otherwise, then the total + the total input must be less than the cap.
if (dollarCap != 0) {
require(_totalCollateralVCBalance.add(_collateralVCInput) <= dollarCap, "Collateral input exceeds cap");
}
uint feePre = _getFeePoint(_totalCollateralVCBalance, _totalVCBalancePre);
uint feePost = _getFeePoint(_totalCollateralVCBalance.add(_collateralVCInput), _totalVCBalancePost);
uint decayedLastFee = calculateDecayedFee();
uint feeCalculated = _max((feePre.add(feePost)).div(2), decayedLastFee);
lastFeeTime = block.timestamp;
lastFeePercent = feeCalculated;
return feeCalculated;
}
/**
* Function for getting the fee for a particular collateral type based on percent of YUSD backed
* by this asset.
*/
function _getFeePoint(uint256 _collateralVCBalance, uint256 _totalVCBalance) internal view returns (uint256 fee) {
if (_totalVCBalance == 0) {
return 0;
}
// percent of all VC backed by this collateral * 1e18
uint256 percentBacked = _collateralVCBalance.mul(1e18).div(_totalVCBalance);
require(percentBacked <= 1e18 && percentBacked >= 0, "percent backed out of bounds");
if (percentBacked <= cutoff1) { // use function 1
return _min(m1.mul(percentBacked).div(1e18).add(b1), 1e18);
} else if (percentBacked <= cutoff2) { // use function 2
return _min(m2.mul(percentBacked).div(1e18).add(b2), 1e18);
} else { // use function 3
return _min(m3.mul(percentBacked).div(1e18).add(b3), 1e18);
}
}
function calculateDecayedFee() public override view returns (uint256 fee) {
uint256 decay = block.timestamp.sub(lastFeeTime);
// Decay within bounds of decay time, then decay the fee.
if (decay <= decayTime) {
fee = lastFeePercent.sub(lastFeePercent.mul(decay).div(decayTime));
} else {
// If it has been longer than decay time, then reset fee to 0.
fee = 0;
}
return fee;
}
function _min(uint256 a, uint256 b) internal pure returns (uint256) {
return a >= b ? b : a;
}
function _max(uint256 a, uint256 b) internal pure returns (uint256) {
return a <= b ? b : a;
}
}