-
Notifications
You must be signed in to change notification settings - Fork 5
/
PoolManager.sol
351 lines (292 loc) · 15.1 KB
/
PoolManager.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
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
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity 0.8.21;
import {TrancheTokenFactoryLike, LiquidityPoolFactoryLike} from "./util/Factory.sol";
import {TrancheTokenLike} from "./token/Tranche.sol";
import {MemberlistLike} from "./token/RestrictionManager.sol";
import {IERC20} from "./interfaces/IERC20.sol";
import {Auth} from "./util/Auth.sol";
import {SafeTransferLib} from "./util/SafeTransferLib.sol";
interface GatewayLike {
function transferTrancheTokensToCentrifuge(
uint64 poolId,
bytes16 trancheId,
address sender,
bytes32 destinationAddress,
uint128 amount
) external;
function transferTrancheTokensToEVM(
uint64 poolId,
bytes16 trancheId,
address sender,
uint64 destinationChainId,
address destinationAddress,
uint128 amount
) external;
function transfer(uint128 currency, address sender, bytes32 recipient, uint128 amount) external;
}
interface LiquidityPoolLike {
function hasMember(address) external returns (bool);
}
interface InvestmentManagerLike {
function liquidityPools(uint64 poolId, bytes16 trancheId, address currency) external returns (address);
function getTrancheToken(uint64 _poolId, bytes16 _trancheId) external view returns (address);
function userEscrow() external view returns (address);
}
interface EscrowLike {
function approve(address token, address spender, uint256 value) external;
}
interface ERC2771Like {
function addLiquidityPool(address forwarder) external;
}
interface AuthLike {
function rely(address usr) external;
}
/// @dev Centrifuge pools
struct Pool {
uint64 poolId;
uint256 createdAt;
mapping(bytes16 => Tranche) tranches;
mapping(address => bool) allowedCurrencies;
}
/// @dev Each Centrifuge pool is associated to 1 or more tranches
struct Tranche {
address token;
uint64 poolId;
bytes16 trancheId;
// important: the decimals of the leading pool currency. Liquidity Pool shares have to be denomatimated with the same precision.
uint8 decimals;
uint256 createdAt;
string tokenName;
string tokenSymbol;
/// @dev Each tranche can have multiple liquidity pools deployed,
/// each linked to a unique investment currency (asset)
mapping(address => address) liquidityPools; // currency -> liquidity pool address
}
/// @title Pool Manager
/// @notice This contract manages which pools & tranches exist,
/// as well as managing allowed pool currencies, and incoming and outgoing transfers.
contract PoolManager is Auth {
uint8 internal constant MAX_CURRENCY_DECIMALS = 18;
EscrowLike public immutable escrow;
LiquidityPoolFactoryLike public immutable liquidityPoolFactory;
TrancheTokenFactoryLike public immutable trancheTokenFactory;
GatewayLike public gateway;
InvestmentManagerLike public investmentManager;
mapping(uint64 => Pool) public pools;
/// @dev Chain agnostic currency id -> evm currency address and reverse mapping
mapping(uint128 => address) public currencyIdToAddress;
mapping(address => uint128) public currencyAddressToId;
// --- Events ---
event File(bytes32 indexed what, address data);
event PoolAdded(uint64 indexed poolId);
event PoolCurrencyAllowed(uint128 indexed currency, uint64 indexed poolId);
event TrancheAdded(uint64 indexed poolId, bytes16 indexed trancheId);
event TrancheDeployed(uint64 indexed poolId, bytes16 indexed trancheId, address indexed token);
event CurrencyAdded(uint128 indexed currency, address indexed currencyAddress);
event LiquidityPoolDeployed(uint64 indexed poolId, bytes16 indexed trancheId, address indexed liquidityPoool);
event TrancheTokenDeployed(uint64 indexed poolId, bytes16 indexed trancheId);
constructor(address escrow_, address liquidityPoolFactory_, address trancheTokenFactory_) {
escrow = EscrowLike(escrow_);
liquidityPoolFactory = LiquidityPoolFactoryLike(liquidityPoolFactory_);
trancheTokenFactory = TrancheTokenFactoryLike(trancheTokenFactory_);
wards[msg.sender] = 1;
emit Rely(msg.sender);
}
/// @dev Gateway must be msg.sender for incoming message handling.
modifier onlyGateway() {
require(msg.sender == address(gateway), "PoolManager/not-the-gateway");
_;
}
// --- Administration ---
function file(bytes32 what, address data) external auth {
if (what == "gateway") gateway = GatewayLike(data);
else if (what == "investmentManager") investmentManager = InvestmentManagerLike(data);
else revert("PoolManager/file-unrecognized-param");
emit File(what, data);
}
// --- Outgoing message handling ---
function transfer(address currencyAddress, bytes32 recipient, uint128 amount) public {
uint128 currency = currencyAddressToId[currencyAddress];
require(currency != 0, "PoolManager/unknown-currency");
SafeTransferLib.safeTransferFrom(currencyAddress, msg.sender, address(escrow), amount);
gateway.transfer(currency, msg.sender, recipient, amount);
}
function transferTrancheTokensToCentrifuge(
uint64 poolId,
bytes16 trancheId,
bytes32 destinationAddress,
uint128 amount
) public {
TrancheTokenLike trancheToken = TrancheTokenLike(getTrancheToken(poolId, trancheId));
require(address(trancheToken) != address(0), "PoolManager/unknown-token");
trancheToken.burn(msg.sender, amount);
gateway.transferTrancheTokensToCentrifuge(poolId, trancheId, msg.sender, destinationAddress, amount);
}
function transferTrancheTokensToEVM(
uint64 poolId,
bytes16 trancheId,
uint64 destinationChainId,
address destinationAddress,
uint128 amount
) public {
TrancheTokenLike trancheToken = TrancheTokenLike(getTrancheToken(poolId, trancheId));
require(address(trancheToken) != address(0), "PoolManager/unknown-token");
trancheToken.burn(msg.sender, amount);
gateway.transferTrancheTokensToEVM(
poolId, trancheId, msg.sender, destinationChainId, destinationAddress, amount
);
}
// --- Incoming message handling ---
/// @notice New pool details from an existing Centrifuge pool are added.
/// @dev The function can only be executed by the gateway contract.
function addPool(uint64 poolId) public onlyGateway {
Pool storage pool = pools[poolId];
require(pool.createdAt == 0, "PoolManager/pool-already-added");
pool.poolId = poolId;
pool.createdAt = block.timestamp;
emit PoolAdded(poolId);
}
/// @notice Centrifuge pools can support multiple currencies for investing. this function adds a new supported currency to the pool details.
/// Adding new currencies allow the creation of new liquidity pools for the underlying Centrifuge pool.
/// @dev The function can only be executed by the gateway contract.
function allowPoolCurrency(uint64 poolId, uint128 currency) public onlyGateway {
Pool storage pool = pools[poolId];
require(pool.createdAt != 0, "PoolManager/invalid-pool");
address currencyAddress = currencyIdToAddress[currency];
require(currencyAddress != address(0), "PoolManager/unknown-currency");
pools[poolId].allowedCurrencies[currencyAddress] = true;
emit PoolCurrencyAllowed(currency, poolId);
}
/// @notice New tranche details from an existng Centrifuge pool are added.
/// @dev The function can only be executed by the gateway contract.
function addTranche(
uint64 poolId,
bytes16 trancheId,
string memory tokenName,
string memory tokenSymbol,
uint8 decimals
) public onlyGateway {
Pool storage pool = pools[poolId];
require(pool.createdAt != 0, "PoolManager/invalid-pool");
Tranche storage tranche = pool.tranches[trancheId];
require(tranche.createdAt == 0, "PoolManager/tranche-already-exists");
tranche.poolId = poolId;
tranche.trancheId = trancheId;
tranche.decimals = decimals;
tranche.tokenName = tokenName;
tranche.tokenSymbol = tokenSymbol;
tranche.createdAt = block.timestamp;
emit TrancheAdded(poolId, trancheId);
}
function updateTrancheTokenMetadata(
uint64 poolId,
bytes16 trancheId,
string memory tokenName,
string memory tokenSymbol
) public onlyGateway {
TrancheTokenLike trancheToken = TrancheTokenLike(getTrancheToken(poolId, trancheId));
require(address(trancheToken) != address(0), "PoolManager/unknown-token");
trancheToken.file("name", tokenName);
trancheToken.file("symbol", tokenSymbol);
}
function updateMember(uint64 poolId, bytes16 trancheId, address user, uint64 validUntil) public onlyGateway {
TrancheTokenLike trancheToken = TrancheTokenLike(getTrancheToken(poolId, trancheId));
require(address(trancheToken) != address(0), "PoolManager/unknown-token");
MemberlistLike memberlist = MemberlistLike(address(trancheToken.restrictionManager()));
memberlist.updateMember(user, validUntil);
}
/// @notice A global chain agnostic currency index is maintained on Centrifuge. This function maps a currency from the Centrifuge index to its corresponding address on the evm chain.
/// The chain agnostic currency id has to be used to pass currency information to the Centrifuge.
/// @dev This function can only be executed by the gateway contract.
function addCurrency(uint128 currency, address currencyAddress) public onlyGateway {
// Currency index on the Centrifuge side should start at 1
require(currency != 0, "PoolManager/currency-id-has-to-be-greater-than-0");
require(currencyIdToAddress[currency] == address(0), "PoolManager/currency-id-in-use");
require(currencyAddressToId[currencyAddress] == 0, "PoolManager/currency-address-in-use");
require(IERC20(currencyAddress).decimals() <= MAX_CURRENCY_DECIMALS, "PoolManager/too-many-currency-decimals");
currencyIdToAddress[currency] = currencyAddress;
currencyAddressToId[currencyAddress] = currency;
// Enable taking the currency out of escrow in case of redemptions
EscrowLike(escrow).approve(currencyAddress, investmentManager.userEscrow(), type(uint256).max);
// Enable taking the currency out of escrow in case of decrease invest orders
EscrowLike(escrow).approve(currencyAddress, address(investmentManager), type(uint256).max);
emit CurrencyAdded(currency, currencyAddress);
}
function handleTransfer(uint128 currency, address recipient, uint128 amount) public onlyGateway {
address currencyAddress = currencyIdToAddress[currency];
require(currencyAddress != address(0), "PoolManager/unknown-currency");
EscrowLike(escrow).approve(currencyAddress, address(this), amount);
SafeTransferLib.safeTransferFrom(currencyAddress, address(escrow), recipient, amount);
}
function handleTransferTrancheTokens(uint64 poolId, bytes16 trancheId, address destinationAddress, uint128 amount)
public
onlyGateway
{
TrancheTokenLike trancheToken = TrancheTokenLike(getTrancheToken(poolId, trancheId));
require(address(trancheToken) != address(0), "PoolManager/unknown-token");
require(
MemberlistLike(address(trancheToken.restrictionManager())).hasMember(destinationAddress),
"PoolManager/not-a-member"
);
trancheToken.mint(destinationAddress, amount);
}
// --- Public functions ---
function deployTranche(uint64 poolId, bytes16 trancheId) public returns (address) {
Tranche storage tranche = pools[poolId].tranches[trancheId];
require(tranche.token == address(0), "PoolManager/tranche-already-deployed");
require(tranche.createdAt != 0, "PoolManager/tranche-not-added");
address[] memory trancheTokenWards = new address[](2);
trancheTokenWards[0] = address(investmentManager);
trancheTokenWards[1] = address(this);
address[] memory memberlistWards = new address[](1);
memberlistWards[0] = address(this);
address token = trancheTokenFactory.newTrancheToken(
poolId,
trancheId,
tranche.tokenName,
tranche.tokenSymbol,
tranche.decimals,
trancheTokenWards,
memberlistWards
);
tranche.token = token;
emit TrancheTokenDeployed(poolId, trancheId);
return token;
}
function deployLiquidityPool(uint64 poolId, bytes16 trancheId, address currency) public returns (address) {
Tranche storage tranche = pools[poolId].tranches[trancheId];
require(tranche.token != address(0), "PoolManager/tranche-does-not-exist"); // Tranche must have been added
require(isAllowedAsPoolCurrency(poolId, currency), "PoolManager/currency-not-supported"); // Currency must be supported by pool
address liquidityPool = tranche.liquidityPools[currency];
require(liquidityPool == address(0), "PoolManager/liquidityPool-already-deployed");
require(pools[poolId].createdAt != 0, "PoolManager/pool-does-not-exist");
address[] memory liquidityPoolWards = new address[](1);
liquidityPoolWards[0] = address(investmentManager);
liquidityPool = liquidityPoolFactory.newLiquidityPool(
poolId, trancheId, currency, tranche.token, address(investmentManager), liquidityPoolWards
);
tranche.liquidityPools[currency] = liquidityPool;
AuthLike(address(investmentManager)).rely(liquidityPool);
// Enable LP to take the tranche tokens out of escrow in case if investments
AuthLike(tranche.token).rely(liquidityPool); // Add liquidityPool as ward on tranche token
ERC2771Like(tranche.token).addLiquidityPool(liquidityPool);
EscrowLike(escrow).approve(liquidityPool, address(investmentManager), type(uint256).max); // Approve investment manager on tranche token for coordinating transfers
EscrowLike(escrow).approve(liquidityPool, liquidityPool, type(uint256).max); // Approve liquidityPool on tranche token to be able to burn
emit LiquidityPoolDeployed(poolId, trancheId, liquidityPool);
return liquidityPool;
}
// --- Helpers ---
function getTrancheToken(uint64 poolId, bytes16 trancheId) public view returns (address) {
Tranche storage tranche = pools[poolId].tranches[trancheId];
return tranche.token;
}
function getLiquidityPool(uint64 poolId, bytes16 trancheId, address currency) public view returns (address) {
return pools[poolId].tranches[trancheId].liquidityPools[currency];
}
function isAllowedAsPoolCurrency(uint64 poolId, address currencyAddress) public view returns (bool) {
uint128 currency = currencyAddressToId[currencyAddress];
require(currency != 0, "PoolManager/unknown-currency"); // Currency index on the Centrifuge side should start at 1
require(pools[poolId].allowedCurrencies[currencyAddress], "PoolManager/pool-currency-not-allowed");
return true;
}
}