-
Notifications
You must be signed in to change notification settings - Fork 4
/
MonetarySupervisor.sol
230 lines (191 loc) · 12.3 KB
/
MonetarySupervisor.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
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
/* MonetarySupervisor
- maintains system wide KPIs (eg totalLockAmount, totalLoanAmount)
- holds system wide parameters/limits
- enforces system wide limits
- burns and issues to AugmintReserves
- Send funds from reserve to exchange when intervening (not implemented yet)
- Converts older versions of AugmintTokens in 1:1 to new
*/
pragma solidity 0.4.24;
import "./generic/SafeMath.sol";
import "./generic/Restricted.sol";
import "./interfaces/AugmintTokenInterface.sol";
import "./interfaces/TokenReceiver.sol";
import "./InterestEarnedAccount.sol";
import "./AugmintReserves.sol";
contract MonetarySupervisor is Restricted, TokenReceiver { // solhint-disable-line no-empty-blocks
using SafeMath for uint256;
uint public constant PERCENT_100 = 1000000;
AugmintTokenInterface public augmintToken;
InterestEarnedAccount public interestEarnedAccount;
AugmintReserves public augmintReserves;
uint public issuedByStabilityBoard; // token issued by Stability Board
uint public burnedByStabilityBoard; // token burned by Stability Board
uint public totalLoanAmount; // total amount of all loans without interest, in token
uint public totalLockedAmount; // total amount of all locks without premium, in token
/**********
Parameters to ensure totalLoanAmount or totalLockedAmount difference is within limits and system also works
when total loan or lock amounts are low.
for test calculations: https://docs.google.com/spreadsheets/d/1MeWYPYZRIm1n9lzpvbq8kLfQg1hhvk5oJY6NrR401S0
**********/
struct LtdParams {
uint lockDifferenceLimit; /* only allow a new lock if Loan To Deposit ratio would stay above
(1 - lockDifferenceLimit) with new lock. Stored as parts per million */
uint loanDifferenceLimit; /* only allow a new loan if Loan To Deposit ratio would stay above
(1 + loanDifferenceLimit) with new loan. Stored as parts per million */
/* allowedDifferenceAmount param is to ensure the system is not "freezing" when totalLoanAmount or
totalLockAmount is low.
It allows a new loan or lock (up to an amount to reach this difference) even if LTD will go below / above
lockDifferenceLimit / loanDifferenceLimit with the new lock/loan */
uint allowedDifferenceAmount;
}
LtdParams public ltdParams;
/* Previously deployed AugmintTokens which are accepted for conversion (see transferNotification() )
NB: it's not iterable so old version addresses needs to be added for UI manually after each deploy */
mapping(address => bool) public acceptedLegacyAugmintTokens;
event LtdParamsChanged(uint lockDifferenceLimit, uint loanDifferenceLimit, uint allowedDifferenceAmount);
event AcceptedLegacyAugmintTokenChanged(address augmintTokenAddress, bool newAcceptedState);
event LegacyTokenConverted(address oldTokenAddress, address account, uint amount);
event KPIsAdjusted(uint totalLoanAmountAdjustment, uint totalLockedAmountAdjustment);
event SystemContractsChanged(InterestEarnedAccount newInterestEarnedAccount, AugmintReserves newAugmintReserves);
constructor(address permissionGranterContract, AugmintTokenInterface _augmintToken,
AugmintReserves _augmintReserves, InterestEarnedAccount _interestEarnedAccount,
uint lockDifferenceLimit, uint loanDifferenceLimit, uint allowedDifferenceAmount)
public Restricted(permissionGranterContract) {
augmintToken = _augmintToken;
augmintReserves = _augmintReserves;
interestEarnedAccount = _interestEarnedAccount;
ltdParams = LtdParams(lockDifferenceLimit, loanDifferenceLimit, allowedDifferenceAmount);
}
function issueToReserve(uint amount) external restrict("StabilityBoard") {
issuedByStabilityBoard = issuedByStabilityBoard.add(amount);
augmintToken.issueTo(augmintReserves, amount);
}
function burnFromReserve(uint amount) external restrict("StabilityBoard") {
burnedByStabilityBoard = burnedByStabilityBoard.add(amount);
augmintReserves.burn(augmintToken, amount);
}
/* Locker requesting interest when locking funds. Enforcing LTD to stay within range allowed by LTD params
NB: it does not know about min loan amount, it's the loan contract's responsibility to enforce it */
function requestInterest(uint amountToLock, uint interestAmount) external {
// only whitelisted Locker
require(permissions[msg.sender]["Locker"], "msg.sender must have Locker permission");
require(amountToLock <= getMaxLockAmountAllowedByLtd(), "amountToLock must be <= maxLockAmountAllowedByLtd");
totalLockedAmount = totalLockedAmount.add(amountToLock);
// next line would revert but require to emit reason:
require(augmintToken.balanceOf(address(interestEarnedAccount)) >= interestAmount,
"interestEarnedAccount balance must be >= interestAmount");
interestEarnedAccount.transferInterest(augmintToken, msg.sender, interestAmount); // transfer interest to Locker
}
// Locker notifying when releasing funds to update KPIs
function releaseFundsNotification(uint lockedAmount) external {
// only whitelisted Locker
require(permissions[msg.sender]["Locker"], "msg.sender must have Locker permission");
totalLockedAmount = totalLockedAmount.sub(lockedAmount);
}
/* Issue loan if LTD stays within range allowed by LTD params
NB: it does not know about min loan amount, it's the loan contract's responsibility to enforce it */
function issueLoan(address borrower, uint loanAmount) external {
// only whitelisted LoanManager contracts
require(permissions[msg.sender]["LoanManager"],
"msg.sender must have LoanManager permission");
require(loanAmount <= getMaxLoanAmountAllowedByLtd(), "loanAmount must be <= maxLoanAmountAllowedByLtd");
totalLoanAmount = totalLoanAmount.add(loanAmount);
augmintToken.issueTo(borrower, loanAmount);
}
function loanRepaymentNotification(uint loanAmount) external {
// only whitelisted LoanManager contracts
require(permissions[msg.sender]["LoanManager"],
"msg.sender must have LoanManager permission");
totalLoanAmount = totalLoanAmount.sub(loanAmount);
}
// NB: this is called by Lender contract with the sum of all loans collected in batch
function loanCollectionNotification(uint totalLoanAmountCollected) external {
// only whitelisted LoanManager contracts
require(permissions[msg.sender]["LoanManager"],
"msg.sender must have LoanManager permission");
totalLoanAmount = totalLoanAmount.sub(totalLoanAmountCollected);
}
function setAcceptedLegacyAugmintToken(address legacyAugmintTokenAddress, bool newAcceptedState)
external restrict("StabilityBoard") {
acceptedLegacyAugmintTokens[legacyAugmintTokenAddress] = newAcceptedState;
emit AcceptedLegacyAugmintTokenChanged(legacyAugmintTokenAddress, newAcceptedState);
}
function setLtdParams(uint lockDifferenceLimit, uint loanDifferenceLimit, uint allowedDifferenceAmount)
external restrict("StabilityBoard") {
ltdParams = LtdParams(lockDifferenceLimit, loanDifferenceLimit, allowedDifferenceAmount);
emit LtdParamsChanged(lockDifferenceLimit, loanDifferenceLimit, allowedDifferenceAmount);
}
/* function to migrate old totalLoanAmount and totalLockedAmount from old monetarySupervisor contract
when it's upgraded.
Set new monetarySupervisor contract in all locker and loanManager contracts before executing this */
function adjustKPIs(uint totalLoanAmountAdjustment, uint totalLockedAmountAdjustment)
external restrict("StabilityBoard") {
totalLoanAmount = totalLoanAmount.add(totalLoanAmountAdjustment);
totalLockedAmount = totalLockedAmount.add(totalLockedAmountAdjustment);
emit KPIsAdjusted(totalLoanAmountAdjustment, totalLockedAmountAdjustment);
}
/* to allow upgrades of InterestEarnedAccount and AugmintReserves contracts. */
function setSystemContracts(InterestEarnedAccount newInterestEarnedAccount, AugmintReserves newAugmintReserves)
external restrict("StabilityBoard") {
interestEarnedAccount = newInterestEarnedAccount;
augmintReserves = newAugmintReserves;
emit SystemContractsChanged(newInterestEarnedAccount, newAugmintReserves);
}
/* User can request to convert their tokens from older AugmintToken versions in 1:1
transferNotification is called from AugmintToken's transferAndNotify
Flow for converting old tokens:
1) user calls old token contract's transferAndNotify with the amount to convert,
addressing the new MonetarySupervisor Contract
2) transferAndNotify transfers user's old tokens to the current MonetarySupervisor contract's address
3) transferAndNotify calls MonetarySupervisor.transferNotification
4) MonetarySupervisor checks if old AugmintToken is permitted
5) MonetarySupervisor issues new tokens to user's account in current AugmintToken
6) MonetarySupervisor burns old tokens from own balance
*/
function transferNotification(address from, uint amount, uint /* data, not used */ ) external {
AugmintTokenInterface legacyToken = AugmintTokenInterface(msg.sender);
require(acceptedLegacyAugmintTokens[legacyToken], "msg.sender must be allowed in acceptedLegacyAugmintTokens");
legacyToken.burn(amount);
augmintToken.issueTo(from, amount);
emit LegacyTokenConverted(msg.sender, from, amount);
}
/* Helper function for UI.
Returns max lock amount based on minLockAmount, interestPt, using LTD params & interestEarnedAccount balance */
function getMaxLockAmount(uint minLockAmount, uint interestPt) external view returns (uint maxLock) {
uint allowedByEarning = augmintToken.balanceOf(address(interestEarnedAccount)).mul(PERCENT_100).div(interestPt);
uint allowedByLtd = getMaxLockAmountAllowedByLtd();
maxLock = allowedByEarning < allowedByLtd ? allowedByEarning : allowedByLtd;
maxLock = maxLock < minLockAmount ? 0 : maxLock;
}
/* Helper function for UI.
Returns max loan amount based on minLoanAmont using LTD params */
function getMaxLoanAmount(uint minLoanAmount) external view returns (uint maxLoan) {
uint allowedByLtd = getMaxLoanAmountAllowedByLtd();
maxLoan = allowedByLtd < minLoanAmount ? 0 : allowedByLtd;
}
/* returns maximum lockable token amount allowed by LTD params. */
function getMaxLockAmountAllowedByLtd() public view returns(uint maxLockByLtd) {
uint allowedByLtdDifferencePt = totalLoanAmount.mul(PERCENT_100).div(PERCENT_100
.sub(ltdParams.lockDifferenceLimit));
allowedByLtdDifferencePt = totalLockedAmount >= allowedByLtdDifferencePt ?
0 : allowedByLtdDifferencePt.sub(totalLockedAmount);
uint allowedByLtdDifferenceAmount =
totalLockedAmount >= totalLoanAmount.add(ltdParams.allowedDifferenceAmount) ?
0 : totalLoanAmount.add(ltdParams.allowedDifferenceAmount).sub(totalLockedAmount);
maxLockByLtd = allowedByLtdDifferencePt > allowedByLtdDifferenceAmount ?
allowedByLtdDifferencePt : allowedByLtdDifferenceAmount;
}
/* returns maximum borrowable token amount allowed by LTD params */
function getMaxLoanAmountAllowedByLtd() public view returns(uint maxLoanByLtd) {
uint allowedByLtdDifferencePt = totalLockedAmount.mul(ltdParams.loanDifferenceLimit.add(PERCENT_100))
.div(PERCENT_100);
allowedByLtdDifferencePt = totalLoanAmount >= allowedByLtdDifferencePt ?
0 : allowedByLtdDifferencePt.sub(totalLoanAmount);
uint allowedByLtdDifferenceAmount =
totalLoanAmount >= totalLockedAmount.add(ltdParams.allowedDifferenceAmount) ?
0 : totalLockedAmount.add(ltdParams.allowedDifferenceAmount).sub(totalLoanAmount);
maxLoanByLtd = allowedByLtdDifferencePt > allowedByLtdDifferenceAmount ?
allowedByLtdDifferencePt : allowedByLtdDifferenceAmount;
}
}