/
SpigotedLine.sol
273 lines (233 loc) · 9.79 KB
/
SpigotedLine.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
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
pragma solidity ^0.8.9;
import { Denominations } from "chainlink/Denominations.sol";
import { ReentrancyGuard } from "openzeppelin/security/ReentrancyGuard.sol";
import {LineOfCredit} from "./LineOfCredit.sol";
import {LineLib} from "../../utils/LineLib.sol";
import {CreditLib} from "../../utils/CreditLib.sol";
import {SpigotedLineLib} from "../../utils/SpigotedLineLib.sol";
import {MutualConsent} from "../../utils/MutualConsent.sol";
import {ISpigot} from "../../interfaces/ISpigot.sol";
import {ISpigotedLine} from "../../interfaces/ISpigotedLine.sol";
import { IERC20 } from "openzeppelin/token/ERC20/IERC20.sol";
import {SafeERC20} from "openzeppelin/token/ERC20/utils/SafeERC20.sol";
/**
* @title - Debt DAO Spigoted Line of Credit
* @author - Kiba Gateaux
* @notice - The SpigotedLine is a LineofCredit contract with additional functionality for integrating with a Spigot.
- allows Borrower or Lender to repay debt using collateralized revenue streams
* @dev - Inherits LineOfCredit functionality
*/
contract SpigotedLine is ISpigotedLine, LineOfCredit, ReentrancyGuard {
using SafeERC20 for IERC20;
/// see Spigot
ISpigot public immutable spigot;
/// @notice exchange aggregator (mainly 0x router) to trade revenue tokens from a Spigot for credit tokens owed to lenders
address payable public immutable swapTarget;
/// @notice % of revenue tokens to take from Spigot if the Line of Credit is healthy. 0 decimals
uint8 public immutable defaultRevenueSplit;
/**
* @notice - excess unsold revenue claimed from Spigot to be sold later or excess credit tokens bought from revenue but not yet used to repay debt
* - needed because the Line of Credit might have the same token being lent/borrower as being bought/sold so need to separate accounting.
* @dev - private variable so other Line modules do not interfer with Spigot functionality
*/
mapping(address => uint256) private unusedTokens;
/**
* @notice - The SpigotedLine is a LineofCredit contract with additional functionality for integrating with a Spigot.
- allows Borrower or Lender to repay debt using collateralized revenue streams
* @param oracle_ - price oracle to use for getting all token values
* @param arbiter_ - neutral party with some special priviliges on behalf of borrower and lender
* @param borrower_ - the debitor for all credit positions in this contract
* @param spigot_ - Spigot smart contract that is owned by this Line
* @param swapTarget_ - 0x protocol exchange address to send calldata for trades to exchange revenue tokens for credit tokens
* @param ttl_ - time to live for line of credit contract across all lenders set at deployment in order to set the term/expiry date
* @param defaultRevenueSplit_ - The % of Revenue Tokens that the Spigot escrows for debt repayment if the Line is healthy.
*/
constructor(
address oracle_,
address arbiter_,
address borrower_,
address spigot_,
address payable swapTarget_,
uint256 ttl_,
uint8 defaultRevenueSplit_
) LineOfCredit(oracle_, arbiter_, borrower_, ttl_) {
require(defaultRevenueSplit_ <= SpigotedLineLib.MAX_SPLIT);
spigot = ISpigot(spigot_);
defaultRevenueSplit = defaultRevenueSplit_;
swapTarget = swapTarget_;
}
/**
* see LineOfCredit._init and Securedline.init
* @notice requires this Line is owner of the Escrowed collateral else Line will not init
*/
function _init() internal virtual override(LineOfCredit) returns(LineLib.STATUS) {
if(spigot.owner() != address(this)) return LineLib.STATUS.UNINITIALIZED;
return LineOfCredit._init();
}
function unused(address token) external view returns (uint256) {
return unusedTokens[token];
}
/**
* see SecuredLine.declareInsolvent
* @notice requires Spigot contract itselgf to be transfered to Arbiter and sold off to a 3rd party before declaring insolvent
*(@dev priviliegad internal function.
* @return isInsolvent - if Spigot contract is currently insolvent or not
*/
function _canDeclareInsolvent() internal virtual override returns(bool) {
return SpigotedLineLib.canDeclareInsolvent(address(spigot), arbiter);
}
/// see ISpigotedLine.claimAndRepay
function claimAndRepay(address claimToken, bytes calldata zeroExTradeData)
external
whileBorrowing
nonReentrant
returns (uint256)
{
bytes32 id = ids[0];
Credit memory credit = _accrue(credits[id], id);
if (msg.sender != borrower && msg.sender != credit.lender) {
revert CallerAccessDenied();
}
uint256 newTokens = claimToken == credit.token ?
spigot.claimEscrow(claimToken) : // same asset. dont trade
_claimAndTrade( // trade revenue token for debt obligation
claimToken,
credit.token,
zeroExTradeData
);
uint256 repaid = newTokens + unusedTokens[credit.token];
uint256 debt = credit.interestAccrued + credit.principal;
// cap payment to debt value
if (repaid > debt) repaid = debt;
// update unused amount based on usage
if (repaid > newTokens) {
// using bought + unused to repay line
unusedTokens[credit.token] -= repaid - newTokens;
} else {
// high revenue and bought more than we need
unusedTokens[credit.token] += newTokens - repaid;
}
credits[id] = _repay(credit, id, repaid);
emit RevenuePayment(claimToken, repaid);
return newTokens;
}
/// see ISpigotedLine.useAndRepay
function useAndRepay(uint256 amount) external whileBorrowing returns(bool) {
bytes32 id = ids[0];
Credit memory credit = credits[id];
if (msg.sender != borrower && msg.sender != credit.lender) {
revert CallerAccessDenied();
}
require(amount <= unusedTokens[credit.token]);
unusedTokens[credit.token] -= amount;
credits[id] = _repay(_accrue(credit, id), id, amount);
emit RevenuePayment(credit.token, amount);
return true;
}
/// see ISpigotedLine.claimAndTrade
function claimAndTrade(address claimToken, bytes calldata zeroExTradeData)
external
whileBorrowing
nonReentrant
returns (uint256)
{
require(msg.sender == borrower);
address targetToken = credits[ids[0]].token;
uint256 newTokens = claimToken == targetToken ?
spigot.claimEscrow(claimToken) : // same asset. dont trade
_claimAndTrade( // trade revenue token for debt obligation
claimToken,
targetToken,
zeroExTradeData
);
// add bought tokens to unused balance
unusedTokens[targetToken] += newTokens;
return newTokens;
}
/**
* @notice - Claims revenue tokens escrowed in Spigot and trades them for credit tokens.
* - MUST trade all available claim tokens to target credit token.
* - Excess credit tokens not used to repay dent are stored in `unused`
* @dev - priviliged internal function
* @param claimToken - The revenue token escrowed in the Spigot to sell in trade
* @param targetToken - The credit token that needs to be bought in order to pat down debt. Always `credits[ids[0]].token`
* @param zeroExTradeData - 0x API data to use in trade to sell `claimToken` for target
*
* @return - amount of target tokens bought
*/
function _claimAndTrade(
address claimToken,
address targetToken,
bytes calldata zeroExTradeData
)
internal
returns (uint256)
{
(uint256 tokensBought, uint256 totalUnused) = SpigotedLineLib.claimAndTrade(
claimToken,
targetToken,
swapTarget,
address(spigot),
unusedTokens[claimToken],
zeroExTradeData
);
// we dont use revenue after this so can store now
unusedTokens[claimToken] = totalUnused;
return tokensBought;
}
// SPIGOT OWNER FUNCTIONS
/// see ISpigotedLine.updateOwnerSplit
function updateOwnerSplit(address revenueContract) external returns (bool) {
return SpigotedLineLib.updateSplit(
address(spigot),
revenueContract,
_updateStatus(_healthcheck()),
defaultRevenueSplit
);
}
/// see ISpigotedLine.addSpigot
function addSpigot(
address revenueContract,
ISpigot.Setting calldata setting
)
external
mutualConsent(arbiter, borrower)
returns (bool)
{
return spigot.addSpigot(revenueContract, setting);
}
/// see ISpigotedLine.updateWhitelist
function updateWhitelist(bytes4 func, bool allowed)
external
returns (bool)
{
require(msg.sender == arbiter);
return spigot.updateWhitelistedFunction(func, allowed);
}
/// see ISpigotedLine.releaseSpigot
function releaseSpigot(address to) external returns (bool) {
return SpigotedLineLib.releaseSpigot(
address(spigot),
_updateStatus(_healthcheck()),
borrower,
arbiter,
to
);
}
/// see ISpigotedLine.sweep
function sweep(address to, address token) external nonReentrant returns (uint256) {
uint256 amount = unusedTokens[token];
delete unusedTokens[token];
bool success = SpigotedLineLib.sweep(
to,
token,
amount,
_updateStatus(_healthcheck()),
borrower,
arbiter
);
return success ? amount : 0;
}
// allow claiming/trading in ETH
receive() external payable {}
}