Skip to content

Commit

Permalink
Single tranche infrastructure (#81)
Browse files Browse the repository at this point in the history
* fix deploy script

* Remove old factory, update deploy script

* Fix restricted token tests

* Rename

* Update factory test

* Format

* Work on token manager, support multiple routers, add permissionless router for tests, remove chainbridge router

* Fix gateway build

* Re organize scripts and remove unused ones

* Set up permissionless deployment script

* Fix deploy build

* Add broadcast

* Change deployer to base contract

* Rewrite deploy script

* Implement token manager

* Fix wiring in tests

* Format

* Fix function signatures

* Re-add missing modifiers

* Remove unused code

* Use auth contract in gateway

* Use auth in router

* Use auth in all routers

* Use auth everywhere

* Consistent internal function naming

* Re-add token manager tests

* Re-add handleTransferTrancheTokens

* Add token manager as ward on liquidity pools

* Update transfer tranche token message

* Fix transferTrancheTokensToEVM

* Fix message test

* Actually fix message test

* Fix message indices

* init single tranche infrastructure

* fixed approvals and tests

* adjust auth in transfers

* Fix token permissions

* Format

* Address review comments

* Remove unnecessary deny

* Add missing events

---------

Co-authored-by: ilinzweilin <alina@centrifuge.io>
  • Loading branch information
Jeroen Offerijns and ilinzweilin authored Aug 25, 2023
1 parent 36adccd commit 19b0d21
Show file tree
Hide file tree
Showing 11 changed files with 333 additions and 202 deletions.
6 changes: 4 additions & 2 deletions script/Deployer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {TokenManager} from "src/TokenManager.sol";
import {Escrow} from "src/Escrow.sol";
import {PauseAdmin} from "src/admin/PauseAdmin.sol";
import {DelayedAdmin} from "src/admin/DelayedAdmin.sol";
import {LiquidityPoolFactory, MemberlistFactory} from "src/liquidityPool/Factory.sol";
import {LiquidityPoolFactory, MemberlistFactory, TrancheTokenFactory} from "src/liquidityPool/Factory.sol";
import "forge-std/Script.sol";

interface RouterLike {
Expand All @@ -33,9 +33,11 @@ contract Deployer is Script {

function deployInvestmentManager() public {
address liquidityPoolFactory = address(new LiquidityPoolFactory());
address trancheTokenFactory = address(new TrancheTokenFactory());
address memberlistFactory_ = address(new MemberlistFactory());
escrow = new Escrow();
investmentManager = new InvestmentManager(address(escrow), liquidityPoolFactory, memberlistFactory_);
investmentManager =
new InvestmentManager(address(escrow), liquidityPoolFactory, trancheTokenFactory, memberlistFactory_);
}

function wire(address router) public {
Expand Down
120 changes: 68 additions & 52 deletions src/InvestmentManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
pragma solidity ^0.8.18;
pragma abicoder v2;

import {LiquidityPoolFactoryLike, MemberlistFactoryLike} from "./liquidityPool/Factory.sol";
import {ERC20Like} from "./token/Restricted.sol";
import {TrancheTokenFactoryLike, LiquidityPoolFactoryLike, MemberlistFactoryLike} from "./liquidityPool/Factory.sol";
import {MemberlistLike} from "./token/Memberlist.sol";
import "./auth/auth.sol";

Expand All @@ -23,7 +22,6 @@ interface GatewayLike {
interface LiquidityPoolLike {
function rely(address) external;
// restricted token functions
function memberlist() external returns (address);
function hasMember(address) external returns (bool);
function file(bytes32 what, address data) external;
// erc20 functions
Expand All @@ -32,7 +30,6 @@ interface LiquidityPoolLike {
function balanceOf(address) external returns (uint256);
function transferFrom(address, address, uint256) external returns (bool);
// 4626 functions
function updateTokenPrice(uint128 _tokenPrice) external;
function asset() external returns (address);
// centrifuge chain info functions
function poolId() external returns (uint64);
Expand All @@ -44,10 +41,24 @@ interface TokenManagerLike {
function currencyAddressToId(address addr) external view returns (uint128);
}

interface TrancheTokenLike {
function updateTokenPrice(uint128 _tokenPrice) external;
function memberlist() external returns (address);
}

interface ERC20Like {
function transferFrom(address from, address to, uint256 amount) external returns (bool);
function balanceOf(address) external returns (uint256);
}

interface EscrowLike {
function approve(address token, address spender, uint256 value) external;
}

interface AuthLike {
function rely(address usr) external;
}

/// @dev centrifuge chain pool
struct Pool {
uint64 poolId;
Expand All @@ -57,14 +68,14 @@ struct Pool {

/// @dev centrifuge chain tranche
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;
address[] liquidityPools;
}

/// @dev liquidity pool orders and deposit/redemption limits per user
Expand All @@ -88,6 +99,7 @@ contract InvestmentManager is Auth {

// factories for liquidity pool deployments
LiquidityPoolFactoryLike public immutable liquidityPoolFactory;
TrancheTokenFactoryLike public immutable trancheTokenFactory;
MemberlistFactoryLike public immutable memberlistFactory;

uint256 constant MAX_UINT256 = type(uint256).max;
Expand All @@ -102,26 +114,23 @@ contract InvestmentManager is Auth {
event DepositProcessed(address indexed liquidityPool, address indexed user, uint128 indexed currencyAmount);
event RedemptionProcessed(address indexed liquidityPool, address indexed user, uint128 indexed trancheTokenAmount);
event LiquidityPoolDeployed(uint64 indexed poolId, bytes16 indexed trancheId, address indexed liquidityPoool);

constructor(address escrow_, address liquidityPoolFactory_, address memberlistFactory_) {
event TrancheTokenDeployed(uint64 indexed poolId, bytes16 indexed trancheId);

constructor(
address escrow_,
address liquidityPoolFactory_,
address trancheTokenFactory_,
address memberlistFactory_
) {
escrow = EscrowLike(escrow_);
liquidityPoolFactory = LiquidityPoolFactoryLike(liquidityPoolFactory_);
trancheTokenFactory = TrancheTokenFactoryLike(trancheTokenFactory_);
memberlistFactory = MemberlistFactoryLike(memberlistFactory_);

wards[msg.sender] = 1;
emit Rely(msg.sender);
}

// --- Getters ---
/// @dev returns all existing liquidity pools for a centrifuge tranche
function getLiquidityPoolsForTranche(uint64 _poolId, bytes16 _trancheId)
public
view
returns (address[] memory lPools)
{
lPools = tranches[_poolId][_trancheId].liquidityPools;
}

/// @dev gateway must be message.sender. permissions check for incoming message handling.
modifier onlyGateway() {
require(msg.sender == address(gateway), "InvestmentManager/not-the-gateway");
Expand Down Expand Up @@ -198,14 +207,14 @@ contract InvestmentManager is Auth {
address _liquidityPool = msg.sender;
LPValues storage lpValues = orderbook[_user][_liquidityPool];
LiquidityPoolLike lPool = LiquidityPoolLike(_liquidityPool);
ERC20Like currency = ERC20Like(lPool.asset());
address currency = lPool.asset();
uint128 currencyAmount = _toUint128(_currencyAmount);

// check if liquidity pool currency is supported by the centrifuge pool
require(_poolCurrencyCheck(lPool.poolId(), lPool.asset()), "InvestmentManager/currency-not-supported");
require(_poolCurrencyCheck(lPool.poolId(), currency), "InvestmentManager/currency-not-supported");
// check if user is allowed to hold the restriced liquidity pool tokens
require(
_liquidityPoolTokensCheck(lPool.poolId(), lPool.trancheId(), lPool.asset(), _user),
_liquidityPoolTokensCheck(lPool.poolId(), lPool.trancheId(), currency, _user),
"InvestmentManager/tranche-tokens-not-supported"
);

Expand All @@ -227,9 +236,9 @@ contract InvestmentManager is Auth {
lpValues.maxRedeem = 0;

// transfer the differene between required and locked currency from user to escrow
require(currency.balanceOf(_user) >= transferAmount, "InvestmentManager/insufficient-balance");
require(ERC20Like(currency).balanceOf(_user) >= transferAmount, "InvestmentManager/insufficient-balance");
require(
currency.transferFrom(_user, address(escrow), transferAmount),
ERC20Like(currency).transferFrom(_user, address(escrow), transferAmount),
"InvestmentManager/currency-transfer-failed"
);
}
Expand Down Expand Up @@ -305,24 +314,15 @@ contract InvestmentManager is Auth {

function updateTokenPrice(uint64 _poolId, bytes16 _trancheId, uint128 _price) public onlyGateway {
Tranche storage tranche = tranches[_poolId][_trancheId];
require(tranche.createdAt > 0, "InvestmentManager/invalid-pool-or-tranche");
for (uint256 i = 0; i < tranche.liquidityPools.length; i++) {
address lPool = tranche.liquidityPools[i];
require(lPool != address(0), "InvestmentManager/invalid-liquidity-pool");
LiquidityPoolLike(lPool).updateTokenPrice(_price);
}
require(tranche.token != address(0), "InvestmentManager/tranche-not-deployed");
TrancheTokenLike(tranche.token).updateTokenPrice(_price);
}

function updateMember(uint64 _poolId, bytes16 _trancheId, address _user, uint64 _validUntil) public onlyGateway {
Tranche storage tranche = tranches[_poolId][_trancheId];
require(tranche.createdAt > 0, "InvestmentManager/invalid-pool-or-tranche");
for (uint256 i = 0; i < tranche.liquidityPools.length; i++) {
address lPool_ = tranche.liquidityPools[i];
require(lPool_ != address(0), "InvestmentManager/invalid-liquidity-pool");
LiquidityPoolLike lPool = LiquidityPoolLike(lPool_);
MemberlistLike memberlist = MemberlistLike(lPool.memberlist());
memberlist.updateMember(_user, _validUntil);
}
require(tranche.token != address(0), "InvestmentManager/tranche-not-deployed");
MemberlistLike memberlist = MemberlistLike(TrancheTokenLike(tranche.token).memberlist());
memberlist.updateMember(_user, _validUntil);
}

function handleExecutedCollectInvest(
Expand Down Expand Up @@ -437,7 +437,6 @@ contract InvestmentManager is Auth {
(currencyAmount <= orderbook[_user][_liquidityPool].maxDeposit && currencyAmount > 0),
"InvestmentManager/amount-exceeds-deposit-limits"
);

(trancheTokenAmount,) = _deposit(0, currencyAmount, _liquidityPool, _user);
}

Expand Down Expand Up @@ -555,38 +554,55 @@ contract InvestmentManager is Auth {
require(liquidityPool == address(0), "InvestmentManager/liquidityPool-already-deployed");
require(pools[_poolId].createdAt > 0, "InvestmentManager/pool-does-not-exist");
Tranche storage tranche = tranches[_poolId][_trancheId];
require(tranche.createdAt != 0, "InvestmentManager/tranche-does-not-exist"); // tranche must have been added
require(tranche.token != address(0), "InvestmentManager/tranche-does-not-exist"); // tranche must have been added
require(_poolCurrencyCheck(_poolId, _currency), "InvestmentManager/currency-not-supported"); // currency must be supported by pool
uint128 currencyId = tokenManager.currencyAddressToId(_currency);

liquidityPool = liquidityPoolFactory.newLiquidityPool(
_poolId, _trancheId, currencyId, _currency, tranche.token, address(this), address(gateway)
);

EscrowLike(escrow).approve(tranche.token, liquidityPool, MAX_UINT256);
liquidityPools[_poolId][_trancheId][_currency] = liquidityPool;
wards[liquidityPool] = 1;
// enable connectors to take the liquidity pool tokens out of escrow in case if investments
AuthLike(tranche.token).rely(liquidityPool); // add liquidityPool as ward on tranche Token

emit LiquidityPoolDeployed(_poolId, _trancheId, liquidityPool);
return liquidityPool;
}

function deployTranche(uint64 _poolId, bytes16 _trancheId) public returns (address) {
Tranche storage tranche = tranches[_poolId][_trancheId];
require(tranche.token == address(0), "InvestmentManager/tranche-already-deployed");
require(tranche.createdAt > 0, "InvestmentManager/tranche-not-added");
// deploy liquidity pool set gateway as admin on liquidityPool & memberlist
address memberlist = memberlistFactory.newMemberlist(address(gateway), address(this));
MemberlistLike(memberlist).updateMember(address(escrow), type(uint256).max); // add escrow to tranche tokens memberlist
liquidityPool = liquidityPoolFactory.newLiquidityPool(

address token = trancheTokenFactory.newTrancheToken(
_poolId,
_trancheId,
currencyId,
_currency,
address(this),
address(gateway),
memberlist,
tranche.tokenName,
tranche.tokenSymbol,
tranche.decimals
tranche.decimals,
address(gateway)
);
LiquidityPoolLike(liquidityPool).rely(address(tokenManager)); // to be able to mint for incoming transfers
AuthLike(token).rely(address(tokenManager)); // to be able to mint for incoming transfers

liquidityPools[_poolId][_trancheId][_currency] = liquidityPool;
wards[liquidityPool] = 1;
tranche.liquidityPools.push(liquidityPool);
// enable connectors to take the liquidity pool tokens out of escrow in case if investments
EscrowLike(escrow).approve(liquidityPool, address(this), MAX_UINT256);

emit LiquidityPoolDeployed(_poolId, _trancheId, liquidityPool);
return liquidityPool;
tranche.token = token;
emit TrancheTokenDeployed(_poolId, _trancheId);
return token;
}

// ------ helper functions
function getTrancheToken(uint64 poolId, bytes16 trancheId) public view returns (address) {
Tranche storage tranche = tranches[poolId][trancheId];
return tranche.token;
}

// TODO: check rounding
function calcDepositPrice(address _user, address _liquidityPool)
public
Expand Down
39 changes: 15 additions & 24 deletions src/TokenManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,14 @@ interface GatewayLike {

interface InvestmentManagerLike {
function liquidityPools(uint64 poolId, bytes16 trancheId, address currency) external returns (address);
function getLiquidityPoolsForTranche(uint64 _poolId, bytes16 _trancheId)
external
view
returns (address[] memory lPools);
function getTrancheToken(uint64 _poolId, bytes16 _trancheId) external view returns (address);
}

interface EscrowLike {
function approve(address token, address spender, uint256 value) external;
}

interface LiquidityPoolLike is ERC20Like {
interface RestrictedTokenLike is ERC20Like {
function hasMember(address user) external view returns (bool);
}

Expand Down Expand Up @@ -88,16 +85,14 @@ contract TokenManager is Auth {
function transferTrancheTokensToCentrifuge(
uint64 poolId,
bytes16 trancheId,
address currency, // we need this as there is liquidityPool per supported currency
bytes32 destinationAddress,
uint128 amount
) public {
LiquidityPoolLike liquidityPool =
LiquidityPoolLike(investmentManager.liquidityPools(poolId, trancheId, currency));
require(address(liquidityPool) != address(0), "TokenManager/unknown-token");
RestrictedTokenLike trancheToken = RestrictedTokenLike(investmentManager.getTrancheToken(poolId, trancheId));
require(address(trancheToken) != address(0), "TokenManager/unknown-token");

require(liquidityPool.balanceOf(msg.sender) >= amount, "TokenManager/insufficient-balance");
liquidityPool.burn(msg.sender, amount);
require(trancheToken.balanceOf(msg.sender) >= amount, "TokenManager/insufficient-balance");
trancheToken.burn(msg.sender, amount);

gateway.transferTrancheTokensToCentrifuge(poolId, trancheId, msg.sender, destinationAddress, amount);
}
Expand All @@ -109,13 +104,11 @@ contract TokenManager is Auth {
address destinationAddress,
uint128 amount
) public {
// TODO: this should get the underlying tranche token once the single tranche token change has been merged
LiquidityPoolLike liquidityPool =
LiquidityPoolLike(investmentManager.getLiquidityPoolsForTranche(poolId, trancheId)[0]);
require(address(liquidityPool) != address(0), "TokenManager/unknown-token");
RestrictedTokenLike trancheToken = RestrictedTokenLike(investmentManager.getTrancheToken(poolId, trancheId));
require(address(trancheToken) != address(0), "TokenManager/unknown-token");

require(liquidityPool.balanceOf(msg.sender) >= amount, "TokenManager/insufficient-balance");
liquidityPool.burn(msg.sender, amount);
require(trancheToken.balanceOf(msg.sender) >= amount, "TokenManager/insufficient-balance");
trancheToken.burn(msg.sender, amount);

gateway.transferTrancheTokensToEVM(
poolId, trancheId, msg.sender, destinationChainId, destinationAddress, amount
Expand All @@ -135,7 +128,7 @@ contract TokenManager is Auth {
currencyIdToAddress[currency] = currencyAddress;
currencyAddressToId[currencyAddress] = currency;

// enable connectors to take the currency out of escrow in case of redemptions
// enable taking the currency out of escrow in case of redemptions
EscrowLike(escrow).approve(currencyAddress, address(investmentManager), type(uint256).max);
EscrowLike(escrow).approve(currencyAddress, address(this), type(uint256).max);
emit CurrencyAdded(currency, currencyAddress);
Expand All @@ -156,12 +149,10 @@ contract TokenManager is Auth {
public
onlyGateway
{
// TODO: this should get the underlying tranche token once the single tranche token change has been merged
LiquidityPoolLike liquidityPool =
LiquidityPoolLike(investmentManager.getLiquidityPoolsForTranche(poolId, trancheId)[0]);
require(address(liquidityPool) != address(0), "TokenManager/unknown-token");
RestrictedTokenLike trancheToken = RestrictedTokenLike(investmentManager.getTrancheToken(poolId, trancheId));
require(address(trancheToken) != address(0), "TokenManager/unknown-token");

require(liquidityPool.hasMember(destinationAddress), "TokenManager/not-a-member");
liquidityPool.mint(destinationAddress, amount);
require(trancheToken.hasMember(destinationAddress), "TokenManager/not-a-member");
trancheToken.mint(destinationAddress, amount);
}
}
Loading

0 comments on commit 19b0d21

Please sign in to comment.