-
Notifications
You must be signed in to change notification settings - Fork 15
/
WildcatMarket.sol
162 lines (145 loc) · 5.09 KB
/
WildcatMarket.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
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.20;
import '../libraries/FeeMath.sol';
import './WildcatMarketBase.sol';
import './WildcatMarketConfig.sol';
import './WildcatMarketToken.sol';
import './WildcatMarketWithdrawals.sol';
contract WildcatMarket is
WildcatMarketBase,
WildcatMarketConfig,
WildcatMarketToken,
WildcatMarketWithdrawals
{
using MathUtils for uint256;
using SafeCastLib for uint256;
using SafeTransferLib for address;
/**
* @dev Apply pending interest, delinquency fees and protocol fees
* to the state and process the pending withdrawal batch if
* one exists and has expired, then update the market's
* delinquency status.
*/
function updateState() external nonReentrant {
MarketState memory state = _getUpdatedState();
_writeState(state);
}
/**
* @dev Deposit up to `amount` underlying assets and mint market tokens
* for `msg.sender`.
*
* The actual deposit amount is limited by the market's maximum deposit
* amount, which is the configured `maxTotalSupply` minus the current
* total supply.
*
* Reverts if the market is closed or if the scaled token amount
* that would be minted for the deposit is zero.
*/
function depositUpTo(
uint256 amount
) public virtual nonReentrant returns (uint256 /* actualAmount */) {
// Get current state
MarketState memory state = _getUpdatedState();
if (state.isClosed) {
revert DepositToClosedMarket();
}
// Reduce amount if it would exceed totalSupply
amount = MathUtils.min(amount, state.maximumDeposit());
// Scale the mint amount
uint104 scaledAmount = state.scaleAmount(amount).toUint104();
if (scaledAmount == 0) revert NullMintAmount();
// Transfer deposit from caller
asset.safeTransferFrom(msg.sender, address(this), amount);
// Cache account data and revert if not authorized to deposit.
Account memory account = _getAccountWithRole(msg.sender, AuthRole.DepositAndWithdraw);
account.scaledBalance += scaledAmount;
_accounts[msg.sender] = account;
emit Transfer(address(0), msg.sender, amount);
emit Deposit(msg.sender, amount, scaledAmount);
// Increase supply
state.scaledTotalSupply += scaledAmount;
// Update stored state
_writeState(state);
return amount;
}
/**
* @dev Deposit exactly `amount` underlying assets and mint market tokens
* for `msg.sender`.
*
* Reverts if the deposit amount would cause the market to exceed the
* configured `maxTotalSupply`.
*/
function deposit(uint256 amount) external virtual {
uint256 actualAmount = depositUpTo(amount);
if (amount != actualAmount) {
revert MaxSupplyExceeded();
}
}
/**
* @dev Withdraw available protocol fees to the fee recipient.
*/
function collectFees() external nonReentrant {
MarketState memory state = _getUpdatedState();
if (state.accruedProtocolFees == 0) {
revert NullFeeAmount();
}
uint128 withdrawableFees = state.withdrawableProtocolFees(totalAssets());
if (withdrawableFees == 0) {
revert InsufficientReservesForFeeWithdrawal();
}
state.accruedProtocolFees -= withdrawableFees;
_writeState(state);
asset.safeTransfer(feeRecipient, withdrawableFees);
emit FeesCollected(withdrawableFees);
}
/**
* @dev Withdraw funds from the market to the borrower.
*
* Can only withdraw up to the assets that are not required
* to meet the borrower's collateral obligations.
*
* Reverts if the market is closed.
*/
function borrow(uint256 amount) external onlyBorrower nonReentrant {
MarketState memory state = _getUpdatedState();
if (state.isClosed) {
revert BorrowFromClosedMarket();
}
uint256 borrowable = state.borrowableAssets(totalAssets());
if (amount > borrowable) {
revert BorrowAmountTooHigh();
}
_writeState(state);
asset.safeTransfer(msg.sender, amount);
emit Borrow(amount);
}
/**
* @dev Sets the market APR to 0% and marks market as closed.
*
* Can not be called if there are any unpaid withdrawal batches.
*
* Transfers remaining debts from borrower if market is not fully
* collateralized; otherwise, transfers any assets in excess of
* debts to the borrower.
*/
function closeMarket() external onlyController nonReentrant {
MarketState memory state = _getUpdatedState();
state.annualInterestBips = 0;
state.isClosed = true;
state.reserveRatioBips = 0;
if (_withdrawalData.unpaidBatches.length() > 0) {
revert CloseMarketWithUnpaidWithdrawals();
}
uint256 currentlyHeld = totalAssets();
uint256 totalDebts = state.totalDebts();
if (currentlyHeld < totalDebts) {
// Transfer remaining debts from borrower
asset.safeTransferFrom(borrower, address(this), totalDebts - currentlyHeld);
} else if (currentlyHeld > totalDebts) {
// Transfer excess assets to borrower
asset.safeTransfer(borrower, currentlyHeld - totalDebts);
}
_writeState(state);
emit MarketClosed(block.timestamp);
}
}