-
Notifications
You must be signed in to change notification settings - Fork 43
Expand file tree
/
Copy pathCollateralManager.sol
More file actions
336 lines (299 loc) · 14.5 KB
/
Copy pathCollateralManager.sol
File metadata and controls
336 lines (299 loc) · 14.5 KB
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
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.28;
import "@nilfoundation/smart-contracts/contracts/Nil.sol";
import "@nilfoundation/smart-contracts/contracts/NilTokenBase.sol";
import "./LendingPool.sol";
/// @title GlobalLedger (Centralized Liquidity and Logic Hub)
/// @notice This contract manages all collateral, loans, and liquidity for the lending protocol.
/// It also handles the deployment and registration of LendingPool contracts across different shards.
/// @dev Acts as the central state and execution core. LendingPool contracts interact with this contract
/// via asynchronous calls to handle user deposits, borrows, and repayments.
contract GlobalLedger is NilTokenBase {
/// @notice The address of the deployer, allowed to call administrative functions.
address public deployer;
/// @notice The address of the InterestManager contract used to fetch interest rates.
address public interestManager;
/// @notice The address of the Oracle contract used to fetch token prices (indirectly via LendingPool).
address public oracle;
/// @notice The TokenId representing the USDT token.
TokenId public usdt;
/// @notice The TokenId representing the ETH token.
TokenId public eth;
// --- Errors --- //
/// @dev Reverts if a function requiring deployer privileges is called by another address.
error OnlyDeployer();
/// @dev Reverts if trying to register or use an invalid (zero) pool address.
error InvalidPoolAddress();
/// @dev Reverts if trying to register a pool on a shard that already has one, or if the pool address is already registered.
error PoolAlreadyRegistered();
/// @dev Reverts if a function requiring a registered LendingPool caller is called by another address.
error UnauthorizedCaller();
/// @dev Reverts during borrow if the user's collateral balance is less than required.
error InsufficientCollateral();
/// @dev Reverts during borrow if the GlobalLedger lacks sufficient liquidity of the requested token.
error InsufficientLiquidity();
/// @dev Reverts during repayment if the amount sent is less than the required repayment (principal + interest).
error RepaymentInsufficient();
/// @dev Reverts during repayment if the user has no active loan or is repaying the wrong token.
error NoActiveLoan();
/// @dev Reverts if an expected cross-shard call (e.g., to Oracle, InterestManager) fails during processing.
error CrossShardCallFailed(string message);
/// @dev Reverts during deployment if an invalid shard ID (0 or >= 0xFFFF) is provided.
error ShardIdInvalid();
// --- Events --- //
/// @notice Emitted when a LendingPool address is successfully registered for a specific shard.
/// @param poolAddress The address of the registered LendingPool contract.
/// @param shardId The shard ID the pool is registered for.
event PoolRegistered(address indexed poolAddress, uint256 shardId);
/// @notice Emitted when a new LendingPool contract is successfully deployed via `deployLendingPools`.
/// @param poolAddress The address of the newly deployed LendingPool contract.
/// @param shardId The shard ID the pool was deployed onto.
event LendingPoolDeployed(address indexed poolAddress, uint256 shardId);
/// @notice Emitted when a deposit forwarded from a LendingPool is successfully processed.
/// @param user The original depositor's address.
/// @param token The TokenId of the deposited asset.
/// @param amount The amount deposited.
event DepositHandled(address indexed user, TokenId token, uint256 amount);
/// @notice Emitted when a borrow request is successfully processed and funds are sent.
/// @param borrower The borrower's address.
/// @param token The TokenId of the borrowed asset.
/// @param amount The amount borrowed.
event BorrowProcessed(
address indexed borrower,
TokenId token,
uint256 amount
);
/// @notice Emitted when a loan repayment is successfully processed (loan record cleared).
/// @param borrower The borrower's address.
/// @param token The TokenId of the repaid asset (loan token).
/// @param amount The principal amount of the loan that was cleared.
event RepaymentProcessed(
address indexed borrower,
TokenId token,
uint256 amount
);
/// @notice Emitted when collateral is successfully returned to the borrower after repayment.
/// @param borrower The borrower's address.
/// @param token The TokenId of the collateral asset returned.
/// @param amount The amount of collateral returned.
event CollateralReturned(
address indexed borrower,
TokenId token,
uint256 amount
);
// --- State Variables --- //
/// @notice Mapping from shard ID to the registered LendingPool address for that shard.
mapping(uint256 => address) public lendingPoolsByShard;
/// @notice Mapping from an address to a boolean indicating if it is a registered LendingPool.
/// @dev Provides a quick lookup for the `onlyRegisteredLendingPool` modifier.
mapping(address => bool) public isLendingPool;
/// @notice Mapping storing user collateral balances.
/// @dev `collateralBalances[userAddress][tokenId] = amount`
mapping(address => mapping(TokenId => uint256)) public collateralBalances;
/// @notice Mapping storing active loan details for each user.
/// @dev `loans[userAddress] = Loan({amount, token})`
mapping(address => Loan) public loans;
/// @dev Struct to store loan details.
struct Loan {
uint256 amount; // Principal amount borrowed
TokenId token; // Token borrowed
}
// --- Modifiers --- //
/// @dev Ensures that the caller is the deployer address.
modifier onlyDeployer() {
if (msg.sender != deployer) revert OnlyDeployer();
_;
}
/// @dev Ensures that the caller is a registered LendingPool contract.
modifier onlyRegisteredLendingPool() {
if (!isLendingPool[msg.sender]) revert UnauthorizedCaller();
_;
}
/// @notice Initializes the GlobalLedger contract.
/// @param _interestManager Address of the InterestManager contract.
/// @param _oracle Address of the Oracle contract.
/// @param _usdt TokenId for USDT.
/// @param _eth TokenId for ETH.
constructor(
address _interestManager,
address _oracle,
TokenId _usdt,
TokenId _eth
) {
deployer = msg.sender;
interestManager = _interestManager;
oracle = _oracle;
usdt = _usdt;
eth = _eth;
}
/// @notice Manually registers a LendingPool contract for a specific shard.
/// @dev Can only be called by the deployer. Primarily used if pools are deployed separately.
/// @param poolAddress The address of the LendingPool to register.
function registerLendingPool(address poolAddress) public onlyDeployer {
if (poolAddress == address(0)) revert InvalidPoolAddress();
uint256 shardId = Nil.getShardId(poolAddress);
// Check if shard already has a pool or if address is already registered
if (
lendingPoolsByShard[shardId] != address(0) ||
isLendingPool[poolAddress]
) {
revert PoolAlreadyRegistered();
}
lendingPoolsByShard[shardId] = poolAddress;
isLendingPool[poolAddress] = true;
emit PoolRegistered(poolAddress, shardId);
}
/// @notice Deploys LendingPool contracts to the specified shards using `Nil.asyncDeploy`.
/// @dev Automatically registers the deployed pools. Skips shards where a pool is already registered.
/// Can only be called by the deployer.
/// @param shardIds An array of shard IDs to deploy LendingPool contracts onto.
function deployLendingPools(uint[] calldata shardIds) public onlyDeployer {
for (uint i = 0; i < shardIds.length; i++) {
uint shardId = shardIds[i];
// Check if a pool already exists for this shard
if (lendingPoolsByShard[shardId] != address(0)) {
continue; // Skip deployment for this shard
}
if (shardId == 0 || shardId >= 0xFFFF) revert ShardIdInvalid();
// Generate a deterministic salt for the deployment
bytes32 salt = keccak256(
abi.encodePacked(deployer, shardId, address(this))
);
// ABI-encode constructor arguments for LendingPool
bytes memory constructorArgs = abi.encode(
address(this), // _centralLedger
interestManager,
oracle,
usdt,
eth
);
// Combine creation code and arguments
bytes memory deploymentCode = bytes.concat(
type(LendingPool).creationCode,
constructorArgs
);
// Deploy asynchronously
address deployedPoolAddress = Nil.asyncDeploy(
shardId,
deployer, // refundTo (deployer initiated this)
deployer, // bounceTo (send error back to deployer if deployment fails)
0, // feeCredit (default)
Nil.FORWARD_REMAINING, // forwardKind (default)
0, // value
deploymentCode,
uint256(salt)
);
// Register the deployed address
lendingPoolsByShard[shardId] = deployedPoolAddress;
isLendingPool[deployedPoolAddress] = true;
emit PoolRegistered(deployedPoolAddress, shardId); // Emit for consistency
emit LendingPoolDeployed(deployedPoolAddress, shardId);
}
}
/// @notice Processes a deposit forwarded asynchronously from a LendingPool contract.
/// @dev Expects the deposited tokens to be included in the transaction (`Nil.txnTokens`).
/// Increases the collateral balance for the original depositor.
/// Can only be called by a registered LendingPool.
/// @param depositor The address of the original user making the deposit.
function handleDeposit(
address depositor
) public payable onlyRegisteredLendingPool {
Nil.Token[] memory tokens = Nil.txnTokens();
TokenId token = tokens[0].id;
uint256 amount = tokens[0].amount;
collateralBalances[depositor][token] += amount;
emit DepositHandled(depositor, token, amount);
}
/// @notice Processes a borrow request forwarded asynchronously from a LendingPool contract.
/// @dev Checks liquidity, verifies collateral, records the loan, and sends the borrowed tokens.
/// Can only be called by a registered LendingPool.
/// @param borrower The address of the user borrowing.
/// @param amount The amount to borrow.
/// @param borrowToken The token to borrow.
/// @param requiredCollateral The minimum collateral value required (pre-calculated by LendingPool).
/// @param collateralToken The token used as collateral.
function handleBorrowRequest(
address borrower,
uint256 amount,
TokenId borrowToken,
uint256 requiredCollateral,
TokenId collateralToken
) public onlyRegisteredLendingPool {
// Check internal liquidity (this contract's balance)
if (Nil.tokenBalance(address(this), borrowToken) < amount) {
revert InsufficientLiquidity();
}
// Check user's collateral balance stored here
if (
collateralBalances[borrower][collateralToken] < requiredCollateral
) {
revert InsufficientCollateral();
}
// Record the loan
loans[borrower] = Loan(amount, borrowToken);
// Send the borrowed tokens directly to the borrower from this contract's funds
sendTokenInternal(borrower, borrowToken, amount);
emit BorrowProcessed(borrower, borrowToken, amount);
}
/// @notice Processes a repayment forwarded asynchronously from a LendingPool contract.
/// @dev Expects the repayment tokens to be included in the transaction (`Nil.txnTokens`).
/// Verifies the repayment amount against the required amount (principal + interest),
/// clears the loan record, and returns the collateral to the borrower.
/// Can only be called by a registered LendingPool.
/// @param borrower The address of the user repaying.
/// @param collateralToken The token used as collateral for the loan being repaid.
/// @param requiredRepaymentAmount The total amount (principal + interest) required (pre-calculated by LendingPool).
function processRepayment(
address borrower,
TokenId collateralToken,
uint256 requiredRepaymentAmount
) public payable onlyRegisteredLendingPool {
Nil.Token[] memory tokens = Nil.txnTokens();
TokenId repaidToken = tokens[0].id;
uint256 sentAmount = tokens[0].amount;
Loan memory loan = loans[borrower];
// Check if there is an active loan and the correct token is being repaid
if (loan.amount == 0 || loan.token != repaidToken) {
revert NoActiveLoan();
}
// Ensure sufficient funds were sent for principal + interest
if (sentAmount < requiredRepaymentAmount) {
revert RepaymentInsufficient();
}
// Clear the loan record
delete loans[borrower];
emit RepaymentProcessed(borrower, repaidToken, loan.amount);
// Handle collateral release
uint256 collateralAmount = collateralBalances[borrower][
collateralToken
];
if (collateralAmount > 0) {
delete collateralBalances[borrower][collateralToken];
sendTokenInternal(borrower, collateralToken, collateralAmount);
emit CollateralReturned(
borrower,
collateralToken,
collateralAmount
);
}
}
/// @notice Fetches a user's collateral balance for a specific token.
/// @param user The address of the user.
/// @param token The token type.
/// @return uint256 The collateral amount.
function getCollateralBalance(
address user,
TokenId token
) public view returns (uint256) {
return collateralBalances[user][token];
}
/// @notice Retrieves a user's active loan details.
/// @param user The address of the user.
/// @return amount_ The loan principal amount (0 if no active loan).
/// @return token_ The token type used for the loan (address(0) if no active loan).
function getLoanDetails(
address user
) public view returns (uint256 amount_, TokenId token_) {
return (loans[user].amount, loans[user].token);
}
}