From 89612cd1de54e3328476b27fb085cdc8ceb46305 Mon Sep 17 00:00:00 2001 From: ilinzweilin Date: Mon, 22 May 2023 14:29:56 +0100 Subject: [PATCH 01/24] add initial tranche4626 draft --- lib/forge-std | 2 +- src/Tranche4626.sol | 168 ++++++++++++++++++++++++++++++++++++++++++ src/auth/auth.sol | 24 ++++++ src/token/erc20.sol | 27 +------ test/Invariants.t.sol | 110 +++++++++++++-------------- 5 files changed, 252 insertions(+), 79 deletions(-) create mode 100644 src/Tranche4626.sol create mode 100644 src/auth/auth.sol diff --git a/lib/forge-std b/lib/forge-std index 4a650f2b..cffb5628 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit 4a650f2bed222c4452383489e4d8cdbaf3d19fc5 +Subproject commit cffb5628775fbf120bfc3c123149f6a6d99dc275 diff --git a/src/Tranche4626.sol b/src/Tranche4626.sol new file mode 100644 index 00000000..420b7354 --- /dev/null +++ b/src/Tranche4626.sol @@ -0,0 +1,168 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.18; + +// Tranche implementation for Centrifuge Pools following the EIP4626 standard. +// Each Tranche is a tokenized vault issuing shares as restricted ERC20 tokens against stable currency deposits based on the current share price. +// tranche vault: tranche asset value. +// asset: The underlying stable currency of the Centrifuge pool. +// share: The restricted ERC-20 Tranche token (TIN/DROP). Has a ratio (token price) of underlying assets exchanged on deposit/withdraw/redeem + +// Challenges: +// 1. Centrifuge Pools and corresponding Tranches live on Centchain having their liquidity spread across multiple chains. +// Latest Tranche values, like share / token price, tranche asset value, total assets... have to be retrieved from Centrifuge chain in order to provide share <-> asset conversions. +// 2. Pool Epochs: Deposits into and redemptions from Centrifuge Pools are subject to epochs. Deposit and redemption orders are collected during 24H epoch periods +// and filled during epoch execution following the rules of the underlying pool. Consequently, deposits and redemptions are not instanty possible and have to follow the epoch schedule. +// Tranche4626 is extending the EIP4626 standard by 'requestRedeem' & 'requestDeposit' functions, where redeem and deposit orders are submitted to the pools to be included in the execution of the following epoch. +// After execution users can use the redeem and withdraw functions to get their shares and/or assets from the pools. + +// other EIP4626 implementations +// maple: https://github.com/maple-labs/pool-v2/blob/301f05b4fe5e9202eef988b4c8321310b4e86dc8/contracts/Pool.sol +// yearn: https://github.com/yearn/yearn-vaults-v3/blob/master/contracts/VaultV3.vy + +import "./token/restricted.sol"; + +interface GatewayLike { + function increaseInvestOrder(uint64 poolId, bytes16 trancheId, address investor, uint128 currency, uint128 amount) + external; + function decreaseInvestOrder(uint64 poolId, bytes16 trancheId, address investor, uint128 currency, uint128 amount) + external; + function increaseRedeemOrder(uint64 poolId, bytes16 trancheId, address investor, uint128 currency, uint128 amount) + external; + function decreaseRedeemOrder(uint64 poolId, bytes16 trancheId, address investor, uint128 currency, uint128 amount) + external; + function collectInvest(uint64 poolId, bytes16 trancheId, address investor) external; + function collectRedeem(uint64 poolId, bytes16 trancheId, address investor) external; +} + +interface EscrowLike { + function approve(address token, address spender, uint256 value) external; +} + +/// @title Tranche4626 +/// @author ilinzweilin +contract Tranche4626 is RestrictedToken { + + EscrowLike public immutable escrow; + GatewayLike public gateway; + + address public asset; // underlying stable ERC-20 stable currency + uint256 public maxAssetDeposit = 2 ** 256 - 1; // max stable currency deposit into the tranche -> default: no limit. + uint64 public poolId; // the id of the Centrifuge pool the Tranche belongs to. + bytes16 trancheId; // the trancheId valid across all chains. + + + uint128 latestPrice; // lates share / token price + uint256 lastPriceUpdate; // timestamp of the latest share / token price update + //TODO: retrieve from Centrifuge chain. + uint256 lastTotalAssets; // latest asset value of the tranche retrieved from the Centrifuge chain + uint256 lastTotalAssetsUpdate; // timestamp of the latest total assets update + + + // ERC4626 events + event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares); + event Withdraw(address indexed sender, address indexed receiver, address indexed owner, uint256 assets, uint256 shares); + + constructor(address _asset, bytes16 _trancheId, uint64 _poolId, address _gateway, address _escrow) { + asset = _asset; + trancheId = _trancheId; + poolId = _poolId; + gateway = gateway; + escrow = _escrow; + + wards[msg.sender] = 1; + emit Rely(msg.sender); + } + + /// @dev The total amount of the undelying assets = the asset value of the tranche retrieved from the Centrifuge chain. + /// @return Total amount of the underlying assets that is managed by Tranche including accrued interest. + function totalAssets() public view returns (uint256) { + return lastTotalAssets; + } + + /// @dev The latest share price is retrieved from the Centrifuge chain. + /// @return The amount of shares / tranche tokens that any user would get for the amount of assets provided. + function convertToShares(uint256 _assets) public view returns (uint256 shares) { + // TODO: it should round DOWN if it’s calculating the amount of shares to issue to a user, given an amount of assets provided. + shares = _assets / latestPrice; + } + + /// @dev The latest share price is retrieved from the Centrifuge chain. + /// @return The amount of assets that any user would get for the amount of shares provided. + function convertToAssets(uint256 _shares) public view returns (uint256 assets) { + // TODO: it should round DOWN if it’s calculating the amount of assets to issue to a user, given an amount of shares provided. + assets = _shares * latestPrice; + } + + /// TODO: do we need to have a custom limit for each receiver? + /// @dev return 0 if deposits are disabled, or 2 ** 256 - 1 if there is no limit. + /// @return Maximum amount of stable currency that can be deposited into the Tranche by the receiver. + function maxDeposit(address _receiver) public view returns (uint256) { + return maxAssetDeposit; + } + + /// TODO: should we run some pool checks that would cause deposit to revert? + /// @return The amount of assets that any user would get for the amount of shares provided -> convertToShares + function previewDeposit(uint256 _assets) public view returns (uint256 shares) { + shares = convertToShares(_assets); + } + + /// TODO: should we also expose public function without auth? + /// @dev request asset deposit for a receiver to be included in the next epoch execution. Asset is locked in the escrow on request submission. + /// @return + function requestDeposit(uint256 _assets, address _receiver) auth public { + require(hasMember(msg.sender), "Tranche4626/not-a-member"); + + require( + ERC20Like(asset).transferFrom(_receiver, address(escrow), _assets), + "Centrifuge/Tranche4626/currency-transfer-failed" + ); + gateway.increaseInvestOrder(poolId, trancheId, _receiver, address(asset), _assets); + } + + /// TODO: should we also expose public function without auth? + /// @dev collect the shares after pool epoch execution. + function deposit(uint256 _assets, address _receiver) auth public returns (uint256 shares) { + _mint(shares_ = previewDeposit(assets_), assets_, _receiver, msg.sender); + emit Deposit(caller_, receiver_, assets_, shares_); + + + } + + /// @dev + /// @return + function maxMint(address receiver) external view returns (uint256 maxShares); + /// @dev + /// @return + function previewMint(uint256 shares) external view returns (uint256 assets); + /// @dev + /// @return + function mint(uint256 shares, address receiver) external returns (uint256 assets); + /// @dev + /// @return + function maxWithdraw(address owner) external view returns (uint256 maxAssets); + /// @dev + /// @return + function previewWithdraw(uint256 assets) external view returns (uint256 shares); + /// @dev + /// @return + function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares); + /// @dev + /// @return + function maxRedeem(address owner) external view returns (uint256 maxShares); + /// @dev + /// @return + function previewRedeem(uint256 shares) external view returns (uint256 assets); + /// @dev + /// @return + function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets); + + function updateTokenPrice(uint128 _tokenPrice) public auth { + latestPrice = _tokenPrice; + lastPriceUpdate = block.timestamp; + } + + function updateTotalAssets(uint128 _tokenPrice) public auth { + latestPrice = _tokenPrice; + lastPriceUpdate = block.timestamp; + } +} diff --git a/src/auth/auth.sol b/src/auth/auth.sol new file mode 100644 index 00000000..165eeedc --- /dev/null +++ b/src/auth/auth.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// Copyright (C) Centrifuge 2020, based on MakerDAO dss https://github.com/makerdao/dss +pragma solidity ^0.8.18; + +contract Auth { + mapping (address => uint256) public wards; + + event Rely(address indexed usr); + event Deny(address indexed usr); + + function rely(address usr) external auth { + wards[usr] = 1; + emit Rely(usr); + } + function deny(address usr) external auth { + wards[usr] = 0; + emit Deny(usr); + } + + modifier auth { + require(wards[msg.sender] == 1, "not-authorized"); + _; + } +} \ No newline at end of file diff --git a/src/token/erc20.sol b/src/token/erc20.sol index 26589ef7..0a3d4190 100644 --- a/src/token/erc20.sol +++ b/src/token/erc20.sol @@ -3,13 +3,14 @@ // Copyright (C) 2021-2022 Foundation pragma solidity ^0.8.18; +import "./../auth/auth.sol"; + interface IERC1271 { function isValidSignature(bytes32, bytes memory) external view returns (bytes4); } // Adapted from https://github.com/makerdao/xdomain-dss/blob/master/src/Dai.sol -contract ERC20 { - mapping(address => uint256) public wards; +contract ERC20 is Auth { string public name; string public symbol; @@ -28,18 +29,14 @@ contract ERC20 { keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); // --- Events --- - event Rely(address indexed user); - event Deny(address indexed user); event File(bytes32 indexed what, string data); event Approval(address indexed owner, address indexed spender, uint256 value); event Transfer(address indexed from, address indexed to, uint256 value); constructor(uint8 decimals_) { decimals = decimals_; - wards[msg.sender] = 1; - emit Rely(msg.sender); - + deploymentChainId = block.chainid; _DOMAIN_SEPARATOR = _calculateDomainSeparator(block.chainid); } @@ -60,22 +57,6 @@ contract ERC20 { return block.chainid == deploymentChainId ? _DOMAIN_SEPARATOR : _calculateDomainSeparator(block.chainid); } - modifier auth() { - require(wards[msg.sender] == 1, "ERC20/not-authorized"); - _; - } - - // --- Administration --- - function rely(address user) external auth { - wards[user] = 1; - emit Rely(user); - } - - function deny(address user) external auth { - wards[user] = 0; - emit Deny(user); - } - function file(bytes32 what, string memory data) external auth { if (what == "name") name = data; else if (what == "symbol") symbol = data; diff --git a/test/Invariants.t.sol b/test/Invariants.t.sol index e43d3a6d..9b0281c7 100644 --- a/test/Invariants.t.sol +++ b/test/Invariants.t.sol @@ -1,55 +1,55 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.18; -pragma abicoder v2; - -import {CentrifugeConnector} from "src/Connector.sol"; -import {ConnectorEscrow} from "src/Escrow.sol"; -import {MockHomeConnector} from "./mock/MockHomeConnector.sol"; -import "./mock/MockXcmRouter.sol"; -import {ConnectorGateway} from "src/routers/Gateway.sol"; -import {TrancheTokenFactory, MemberlistFactory} from "src/token/factory.sol"; -import {InvariantPoolManager} from "./accounts/PoolManager.sol"; -import "forge-std/Test.sol"; -import "../src/Connector.sol"; - -contract ConnectorInvariants is Test { - CentrifugeConnector bridgedConnector; - MockHomeConnector connector; - MockXcmRouter mockXcmRouter; - ConnectorGateway gateway; - - InvariantPoolManager poolManager; - - address[] private targetContracts_; - - function setUp() public { - address escrow_ = address(new ConnectorEscrow()); - address tokenFactory_ = address(new TrancheTokenFactory()); - address memberlistFactory_ = address(new MemberlistFactory()); - bridgedConnector = new CentrifugeConnector(escrow_, tokenFactory_, memberlistFactory_); - mockXcmRouter = new MockXcmRouter(address(bridgedConnector)); - connector = new MockHomeConnector(address(mockXcmRouter)); - gateway = new ConnectorGateway(address(bridgedConnector), address(mockXcmRouter)); - - mockXcmRouter.file("gateway", address(gateway)); - bridgedConnector.file("gateway", address(gateway)); - - // Performs random pool and tranches creations - poolManager = new InvariantPoolManager(connector); - targetContracts_.push(address(poolManager)); - } - - function targetContracts() public view returns (address[] memory) { - return targetContracts_; - } - - // Invariant 1: For every tranche that exists, the equivalent pool exists - function invariantTrancheRequiresPool() external { - for (uint256 i = 0; i < poolManager.allTranchesLength(); i++) { - bytes16 trancheId = poolManager.allTranches(i); - uint64 poolId = poolManager.trancheIdToPoolId(trancheId); - (, uint256 createdAt) = bridgedConnector.pools(poolId); - assertTrue(createdAt > 0); - } - } -} +// // SPDX-License-Identifier: AGPL-3.0-only +// pragma solidity ^0.8.18; +// pragma abicoder v2; + +// import {CentrifugeConnector} from "src/Connector.sol"; +// import {ConnectorEscrow} from "src/Escrow.sol"; +// import {MockHomeConnector} from "./mock/MockHomeConnector.sol"; +// import "./mock/MockXcmRouter.sol"; +// import {ConnectorGateway} from "src/routers/Gateway.sol"; +// import {TrancheTokenFactory, MemberlistFactory} from "src/token/factory.sol"; +// import {InvariantPoolManager} from "./accounts/PoolManager.sol"; +// import "forge-std/Test.sol"; +// import "../src/Connector.sol"; + +// contract ConnectorInvariants is Test { +// CentrifugeConnector bridgedConnector; +// MockHomeConnector connector; +// MockXcmRouter mockXcmRouter; +// ConnectorGateway gateway; + +// InvariantPoolManager poolManager; + +// address[] private targetContracts_; + +// function setUp() public { +// address escrow_ = address(new ConnectorEscrow()); +// address tokenFactory_ = address(new TrancheTokenFactory()); +// address memberlistFactory_ = address(new MemberlistFactory()); +// bridgedConnector = new CentrifugeConnector(escrow_, tokenFactory_, memberlistFactory_); +// mockXcmRouter = new MockXcmRouter(address(bridgedConnector)); +// connector = new MockHomeConnector(address(mockXcmRouter)); +// gateway = new ConnectorGateway(address(bridgedConnector), address(mockXcmRouter)); + +// mockXcmRouter.file("gateway", address(gateway)); +// bridgedConnector.file("gateway", address(gateway)); + +// // Performs random pool and tranches creations +// poolManager = new InvariantPoolManager(connector); +// targetContracts_.push(address(poolManager)); +// } + +// function targetContracts() public view returns (address[] memory) { +// return targetContracts_; +// } + +// // Invariant 1: For every tranche that exists, the equivalent pool exists +// function invariantTrancheRequiresPool() external { +// for (uint256 i = 0; i < poolManager.allTranchesLength(); i++) { +// bytes16 trancheId = poolManager.allTranches(i); +// uint64 poolId = poolManager.trancheIdToPoolId(trancheId); +// (, uint256 createdAt) = bridgedConnector.pools(poolId); +// assertTrue(createdAt > 0); +// } +// } +// } From 1ff48a62f407083595573aa4a1f5ef27271bf202 Mon Sep 17 00:00:00 2001 From: ilinzweilin Date: Mon, 19 Jun 2023 14:54:47 +0100 Subject: [PATCH 02/24] add -deposit/mint flow --- src/Connector.sol | 118 +++++++++++++++++++++++++++++++------ src/Escrow.sol | 24 +------- src/Messages.sol | 40 +++++++++++++ src/Tranche4626.sol | 125 +++++++++++++++++++--------------------- src/routers/Gateway.sol | 26 ++++++++- 5 files changed, 228 insertions(+), 105 deletions(-) diff --git a/src/Connector.sol b/src/Connector.sol index 2c62f40a..6783e41c 100644 --- a/src/Connector.sol +++ b/src/Connector.sol @@ -5,6 +5,7 @@ pragma abicoder v2; import {TrancheTokenFactoryLike, MemberlistFactoryLike} from "./token/factory.sol"; import {RestrictedTokenLike, ERC20Like} from "./token/restricted.sol"; import {MemberlistLike} from "./token/memberlist.sol"; +import "./auth/auth.sol"; interface GatewayLike { function transferTrancheTokensToCentrifuge( @@ -33,6 +34,7 @@ interface GatewayLike { external; function collectInvest(uint64 poolId, bytes16 trancheId, address investor) external; function collectRedeem(uint64 poolId, bytes16 trancheId, address investor) external; + function active() external returns(bool); } interface EscrowLike { @@ -42,6 +44,7 @@ interface EscrowLike { struct Pool { uint64 poolId; uint256 createdAt; + bool isActive; } struct Tranche { @@ -49,16 +52,26 @@ struct Tranche { uint128 latestPrice; // Fixed point integer with 27 decimals uint256 lastPriceUpdate; // TODO: the token name & symbol need to be stored because of the separation between adding and deploying tranches. - // This leads to duplicate storage (also in the ERC20 contract), ideally we should refactor this somehow + // This leads to duplicate storage (also in the ERC20 contract), ideally we should r efactor this somehow string tokenName; string tokenSymbol; uint8 decimals; } -contract CentrifugeConnector { - mapping(address => uint256) public wards; +struct UserTrancheValues { + uint256 maxDeposit; + uint256 maxMint; + uint256 maxWithdraw; + uint256 maxRedeem; + uint256 openRedeem; + uint256 openDeposit; +} + +contract CentrifugeConnector is Auth { + mapping(uint64 => Pool) public pools; mapping(uint64 => mapping(bytes16 => Tranche)) public tranches; + mapping(address => mapping(address => UserTrancheValues)) public orderbook; // contains outstanding orders and limits for each user and tranche mapping(uint128 => address) public currencyIdToAddress; // The reverse mapping of `currencyIdToAddress` @@ -73,8 +86,6 @@ contract CentrifugeConnector { MemberlistFactoryLike public immutable memberlistFactory; // --- Events --- - event Rely(address indexed user); - event Deny(address indexed user); event File(bytes32 indexed what, address data); event CurrencyAdded(uint128 indexed currency, address indexed currencyAddress); event PoolAdded(uint256 indexed poolId); @@ -91,8 +102,13 @@ contract CentrifugeConnector { emit Rely(msg.sender); } - modifier auth() { - require(wards[msg.sender] == 1, "CentrifugeConnector/not-authorized"); + modifier poolActive(uint64 poolId) { + require(pools[poolId].isActive, "CentrifugeConnector/pool-deactivated"); + _; + } + + modifier connectorsActive() { + require(gateway.active(), "CentrifugeConnector/not-the-gateway"); _; } @@ -102,16 +118,6 @@ contract CentrifugeConnector { } // --- Administration --- - function rely(address user) external auth { - wards[user] = 1; - emit Rely(user); - } - - function deny(address user) external auth { - wards[user] = 0; - emit Deny(user); - } - function file(bytes32 what, address data) external auth { if (what == "gateway") gateway = GatewayLike(data); else revert("CentrifugeConnector/file-unrecognized-param"); @@ -119,6 +125,31 @@ contract CentrifugeConnector { } // --- Outgoing message handling --- + + // auth functions + function deposit(uint64 _poolId, address _tranche, address _receiver, uint256 _assets) public poolActive(_poolId) connectorsActive auth returns (uint256) { + require((_assets <= orderbook[_receiver][_tranche].maxDeposit), "CentrifugeConnector/amount-exceeds-deposit-limits"); + uint256 sharePrice = calcUserSharePrice( _receiver, _tranche); + require((sharePrice > 0), "Tranche4626/amount-exceeds-deposit-limits"); + uint256 sharesToTransfer = _assets / sharePrice; + decreaseDepositLimits(_receiver, _tranche, _assets, sharesToTransfer); // decrease the possible deposit limits + ERC20Like erc20 = ERC20Like(_tranche); + require(erc20.transferFrom(address(escrow), _receiver, sharesToTransfer), "CentrifugeConnector/shares-transfer-failed"); + return sharesToTransfer; + } + + function mint(uint64 _poolId, address _tranche, address _receiver, uint256 _shares) public poolActive(_poolId) connectorsActive auth returns (uint256) { + require((_shares <= orderbook[_receiver][_tranche].maxMint), "CentrifugeConnector/amount-exceeds-mint-limits"); + uint256 sharePrice = calcUserSharePrice( _receiver, _tranche); + require((sharePrice > 0), "Tranche4626/amount-exceeds-deposit-limits"); + uint256 requiredDeposit = _shares * sharePrice; + decreaseDepositLimits(_receiver, _tranche, requiredDeposit, _shares); // decrease the possible deposit limits + ERC20Like erc20 = ERC20Like(_tranche); + require(erc20.transferFrom(address(escrow), _receiver, _shares), "CentrifugeConnector/shares-transfer-failed"); + return requiredDeposit; + } + + function transfer(address currencyAddress, bytes32 recipient, uint128 amount) public { uint128 currency = currencyAddressToId[currencyAddress]; require(currency != 0, "CentrifugeConnector/unknown-currency"); @@ -247,6 +278,7 @@ contract CentrifugeConnector { require(pool.createdAt == 0, "CentrifugeConnector/pool-already-added"); pool.poolId = poolId; pool.createdAt = block.timestamp; + pool.isActive = true; emit PoolAdded(poolId); } @@ -334,4 +366,56 @@ contract CentrifugeConnector { require(token.hasMember(destinationAddress), "CentrifugeConnector/not-a-member"); token.mint(destinationAddress, amount); } + + function handleDecreaseInvestOrder(uint64 poolId, bytes16 trancheId, address destinationAddress, address currency, uint128 currencyPayout, uint128 remainingInvestOrder) public onlyGateway {}; + function handleDecreaseRedeemOrder(uint64 poolId, bytes16 trancheId, address destinationAddress, address currency, uint128 trancheTokensPayout, uint128 remainingRedeemOrder) public onlyGateway {}; + function handleCollectInvest(uint64 poolId, bytes16 trancheId, address destinationAddress, address currency, uint128 currencyInvested, uint128 tokensPayout, uint128 remainingInvestOrder) public onlyGateway {}; + function handleCollectRedeem(uint64 poolId, bytes16 trancheId, address destinationAddress, address currency, uint128 currencyPayout, uint128 tokensRedeemed, uint128 remainingRedeemOrder) public onlyGateway {}; + + + // ------ EIP 4626 helper functions + + // function decreaseRedemptionsLimit(uint256 _assets, uint256 _shares) public auth {} + + //TODO: rounding + function decreaseDepositLimits(address _user, address _tranche, uint256 _assets, uint256 _shares) internal { + UserTrancheValues values = orderbook[_user][_tranche]; + if (values.maxDeposit < _assets) { + values.maxDeposit = 0; + } else { + values.maxDeposit = values.maxDeposit - _assets; + } + + if (values.maxMint < _shares) { + values.maxMint = 0; + } else { + values.maxMint = values.maxMint - _shares; + } + } + + /// @dev calculates the avg share price for the deposited assets of a specific user + function calcUserSharePrice(address _user, address _tranche) public view returns (uint256 sharePrice) { + UserTrancheValues values = orderbook[_user][_tranche]; + if(values.maxMint == 0) { + return 0; + } + sharePrice = values.maxDeposit / orderbook[_user].maxMint; + } + + function maxDeposit(address _user, address _tranche) public view returns (uint256) { + return orderbook[_user][_tranche].maxDeposit; + } + + function maxMint(address _user, address _tranche) public view returns (uint256) { + return orderbook[_user][_tranche].maxMint; + } + + function maxWithdraw(address _user, address _tranche) public view returns (uint256) { + return orderbook[_user][_tranche].maxWithdraw; + } + + function maxRedeem(address _user, address _tranche) public view returns (uint256) { + return orderbook[_user][_tranche].maxRedeem; + } + } diff --git a/src/Escrow.sol b/src/Escrow.sol index ca49a248..92ff03ca 100644 --- a/src/Escrow.sol +++ b/src/Escrow.sol @@ -2,17 +2,13 @@ // Copyright (C) 2017, 2018, 2019 dbrock, rain, mrchico // Copyright (C) 2021 Dai Foundation pragma solidity ^0.8.18; +import "./auth/auth.sol"; interface ApproveLike { function approve(address, uint256) external; } -contract ConnectorEscrow { - mapping(address => uint256) public wards; - - // --- Events --- - event Rely(address indexed usr); - event Deny(address indexed usr); +contract ConnectorEscrow is Auth { event Approve(address indexed token, address indexed spender, uint256 value); @@ -21,22 +17,6 @@ contract ConnectorEscrow { emit Rely(msg.sender); } - modifier auth() { - require(wards[msg.sender] == 1, "ConnectorEscrow/not-authorized"); - _; - } - - // --- Administration --- - function rely(address usr) external auth { - wards[usr] = 1; - emit Rely(usr); - } - - function deny(address usr) external auth { - wards[usr] = 0; - emit Deny(usr); - } - // --- Token approvals --- function approve(address token, address spender, uint256 value) external auth { emit Approve(token, spender, value); diff --git a/src/Messages.sol b/src/Messages.sol index c33c0061..51b5a99b 100644 --- a/src/Messages.sol +++ b/src/Messages.sol @@ -361,6 +361,46 @@ library ConnectorMessages { amount = uint128(_msg.indexUint(98, 16)); } +// function parseCollectRedeem(bytes29 _msg) +// internal +// pure +// returns (uint64 poolId, bytes16 trancheId, address destinationAddress, address currency, uint128 currencyPayout, uint128 tokensRedeemed, uint128 remainingRedeemOrder) {} + +// function isCollectRedeem(bytes29 _msg) +// internal +// pure +// returns (uint64 poolId, bytes16 trancheId, address destinationAddress, address currency, uint128 currencyPayout, uint128 tokensRedeemed, uint128 remainingRedeemOrder) {} + +// function parseCollectInvest(bytes29 _msg) +// internal +// pure +// returns (uint64 poolId, bytes16 trancheId, address destinationAddress, address currency, uint128 currencyInvested, uint128 tokensPayout, uint128 remainingInvestOrder){} + +// function isCollectInvest(bytes29 _msg) +// internal +// pure +// returns (uint64 poolId, bytes16 trancheId, address destinationAddress, address currency, uint128 currencyInvested, uint128 tokensPayout, uint128 remainingInvestOrder){} + +// function parseDecreaseInvestOrder(bytes29 _msg) +// internal +// pure +// returns (uint64 poolId, bytes16 trancheId, address destinationAddress, address currency, uint128 currencyPayout, uint128 remainingInvestOrder){} + +// function isDecreaseInvestOrder(bytes29 _msg) +// internal +// pure +// returns (uint64 poolId, bytes16 trancheId, address destinationAddress, address currency, uint128 currencyPayout, uint128 remainingInvestOrder){} + +// function parseDecreaseRedeemOrder(bytes29 _msg) +// internal +// pure +// returns (uint64 poolId, bytes16 trancheId, address destinationAddress, address currency, uint128 currencyPayout, uint128 remainingInvestOrder){} + +// function isDecreaseRedeemOrder(bytes29 _msg) +// internal +// pure +// returns (uint64 poolId, bytes16 trancheId, address destinationAddress, address currency, uint128 currencyPayout, uint128 remainingInvestOrder){} + /* * IncreaseInvestOrder Message * diff --git a/src/Tranche4626.sol b/src/Tranche4626.sol index 420b7354..eab7dbf1 100644 --- a/src/Tranche4626.sol +++ b/src/Tranche4626.sol @@ -19,127 +19,115 @@ pragma solidity ^0.8.18; // maple: https://github.com/maple-labs/pool-v2/blob/301f05b4fe5e9202eef988b4c8321310b4e86dc8/contracts/Pool.sol // yearn: https://github.com/yearn/yearn-vaults-v3/blob/master/contracts/VaultV3.vy -import "./token/restricted.sol"; -interface GatewayLike { - function increaseInvestOrder(uint64 poolId, bytes16 trancheId, address investor, uint128 currency, uint128 amount) - external; - function decreaseInvestOrder(uint64 poolId, bytes16 trancheId, address investor, uint128 currency, uint128 amount) - external; - function increaseRedeemOrder(uint64 poolId, bytes16 trancheId, address investor, uint128 currency, uint128 amount) - external; - function decreaseRedeemOrder(uint64 poolId, bytes16 trancheId, address investor, uint128 currency, uint128 amount) - external; - function collectInvest(uint64 poolId, bytes16 trancheId, address investor) external; - function collectRedeem(uint64 poolId, bytes16 trancheId, address investor) external; -} -interface EscrowLike { - function approve(address token, address spender, uint256 value) external; +// create deposit flow in connectors +// create mint flow +// create redeem flow +// create withdraw flow +// messages collectRedeem & collectInvest + + +import "./token/restricted.sol"; + +interface ConnectorLike { + function deposit(uint64 _poolId, address _tranche, address _receiver, uint256 _assets) external returns (uint256); + function mint(uint64 _poolId, address _tranche, address _receiver, uint256 _shares) external returns (uint256); + function maxDeposit(address _user, address _tranche) external returns (uint256); + function maxMint(address _user, address _tranche) external returns (uint256); + function maxWithdraw(address _user, address _tranche) external returns (uint256); + function maxRedeem(address _user, address _tranche) external returns (uint256) } /// @title Tranche4626 /// @author ilinzweilin contract Tranche4626 is RestrictedToken { - EscrowLike public immutable escrow; - GatewayLike public gateway; + ConnectorLike public connector; - address public asset; // underlying stable ERC-20 stable currency + address public asset; // underlying stable ERC-20 stable currency. uint256 public maxAssetDeposit = 2 ** 256 - 1; // max stable currency deposit into the tranche -> default: no limit. uint64 public poolId; // the id of the Centrifuge pool the Tranche belongs to. bytes16 trancheId; // the trancheId valid across all chains. - uint128 latestPrice; // lates share / token price uint256 lastPriceUpdate; // timestamp of the latest share / token price update - //TODO: retrieve from Centrifuge chain. - uint256 lastTotalAssets; // latest asset value of the tranche retrieved from the Centrifuge chain - uint256 lastTotalAssetsUpdate; // timestamp of the latest total assets update - - + // ERC4626 events event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares); event Withdraw(address indexed sender, address indexed receiver, address indexed owner, uint256 assets, uint256 shares); - constructor(address _asset, bytes16 _trancheId, uint64 _poolId, address _gateway, address _escrow) { + constructor(address _asset, bytes16 _trancheId, uint64 _poolId, address _connector) { asset = _asset; trancheId = _trancheId; poolId = _poolId; - gateway = gateway; - escrow = _escrow; + connector = ConnectorLike(_connector); wards[msg.sender] = 1; emit Rely(msg.sender); } - /// @dev The total amount of the undelying assets = the asset value of the tranche retrieved from the Centrifuge chain. - /// @return Total amount of the underlying assets that is managed by Tranche including accrued interest. + /// @dev The total amount of vault shares. + /// @return Total amount of the underlying vault assets including accrued interest. function totalAssets() public view returns (uint256) { - return lastTotalAssets; + return totalSupply() * latestPrice; } - /// @dev The latest share price is retrieved from the Centrifuge chain. - /// @return The amount of shares / tranche tokens that any user would get for the amount of assets provided. + /// @dev Calculates the amount of shares / tranche tokens that any user would get for the amount of assets provided. The calcultion is based on the token price from the most recent epoch retrieved from Centrifuge chain. function convertToShares(uint256 _assets) public view returns (uint256 shares) { // TODO: it should round DOWN if it’s calculating the amount of shares to issue to a user, given an amount of assets provided. shares = _assets / latestPrice; } - /// @dev The latest share price is retrieved from the Centrifuge chain. - /// @return The amount of assets that any user would get for the amount of shares provided. + /// @dev Calculates the asset value for an amount of shares / tranche tokens provided. The calcultion is based on the token price from the most recent epoch retrieved from Centrifuge chain. function convertToAssets(uint256 _shares) public view returns (uint256 assets) { // TODO: it should round DOWN if it’s calculating the amount of assets to issue to a user, given an amount of shares provided. assets = _shares * latestPrice; } - /// TODO: do we need to have a custom limit for each receiver? - /// @dev return 0 if deposits are disabled, or 2 ** 256 - 1 if there is no limit. - /// @return Maximum amount of stable currency that can be deposited into the Tranche by the receiver. + /// @return Maximum amount of stable currency that can be deposited into the Tranche by the receiver after the epoch had been executed on Centrifuge chain. function maxDeposit(address _receiver) public view returns (uint256) { - return maxAssetDeposit; + return connector.maxDeposit(_receiver, address(this)); } - /// TODO: should we run some pool checks that would cause deposit to revert? - /// @return The amount of assets that any user would get for the amount of shares provided -> convertToShares + /// @return The amount of shares that any user would get for an amount of assets provided -> convertToShares function previewDeposit(uint256 _assets) public view returns (uint256 shares) { shares = convertToShares(_assets); } - /// TODO: should we also expose public function without auth? /// @dev request asset deposit for a receiver to be included in the next epoch execution. Asset is locked in the escrow on request submission. - /// @return function requestDeposit(uint256 _assets, address _receiver) auth public { - require(hasMember(msg.sender), "Tranche4626/not-a-member"); - - require( - ERC20Like(asset).transferFrom(_receiver, address(escrow), _assets), - "Centrifuge/Tranche4626/currency-transfer-failed" - ); - gateway.increaseInvestOrder(poolId, trancheId, _receiver, address(asset), _assets); } - /// TODO: should we also expose public function without auth? - /// @dev collect the shares after pool epoch execution. + /// @dev collect shares for deposited funds after pool epoch execution. maxMint is the max amount of shares that can be collected. Required assets must already be locked. + /// maxDeposit is the amount of funds that was successfully invested into the pool on Centrifuge chain function deposit(uint256 _assets, address _receiver) auth public returns (uint256 shares) { - _mint(shares_ = previewDeposit(assets_), assets_, _receiver, msg.sender); - emit Deposit(caller_, receiver_, assets_, shares_); + uint transferredShares = connector.deposit(poolId, address(this), _receiver, _assets); + Deposit(address(this), _receiver, _assets, transferredShares); + } - + /// @dev collect shares for deposited funds after pool epoch execution. maxMint is the max amount of shares that can be collected. Required assets must already be locked. + /// maxDeposit is the amount of funds that was successfully invested into the pool on Centrifuge chain + function mint(uint256 _shares, address _receiver) auth public returns (uint256 assets) { + uint lockedAssets = connector.mint(poolId, address(this), _receiver, _shares); + Deposit(address(this), _receiver, lockedAssets, _shares); + } + + /// @dev Maximum amount of shares that can be claimed by the receiver after the epoch has been executed on the Centrifuge chain side. + function maxMint(address receiver) external view returns (uint256 maxShares) { + return connector.maxMint(_receiver, address(this)); + } + + /// @return The amount of assets that any user would get for an amount of shares provided -> convertToAssets + function previewMint(uint256 _shares) external view returns (uint256 assets) { + assets = convertToAssets(_shares); } /// @dev /// @return - function maxMint(address receiver) external view returns (uint256 maxShares); - /// @dev - /// @return - function previewMint(uint256 shares) external view returns (uint256 assets); - /// @dev - /// @return - function mint(uint256 shares, address receiver) external returns (uint256 assets); - /// @dev - /// @return - function maxWithdraw(address owner) external view returns (uint256 maxAssets); + function maxWithdraw(address _owner) external view returns (uint256 maxAssets) { + return connector.maxWithdraw(_owner, address(this)); + } /// @dev /// @return function previewWithdraw(uint256 assets) external view returns (uint256 shares); @@ -148,7 +136,10 @@ contract Tranche4626 is RestrictedToken { function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares); /// @dev /// @return - function maxRedeem(address owner) external view returns (uint256 maxShares); + function maxRedeem(address owner) external view returns (uint256 maxShares) { + return connector.maxRedeem(_owner, address(this)); + } + /// @dev /// @return function previewRedeem(uint256 shares) external view returns (uint256 assets); @@ -165,4 +156,8 @@ contract Tranche4626 is RestrictedToken { latestPrice = _tokenPrice; lastPriceUpdate = block.timestamp; } + + + + } diff --git a/src/routers/Gateway.sol b/src/routers/Gateway.sol index 0983aad8..bb2efac1 100644 --- a/src/routers/Gateway.sol +++ b/src/routers/Gateway.sol @@ -22,6 +22,10 @@ interface ConnectorLike { function handleTransfer(uint128 currency, address recipient, uint128 amount) external; function handleTransferTrancheTokens(uint64 poolId, bytes16 trancheId, address destinationAddress, uint128 amount) external; + function handleDecreaseInvestOrder(uint64 poolId, bytes16 trancheId, address destinationAddress, address currency, uint128 currencyPayout, uint128 remainingInvestOrder) external; + function handleDecreaseRedeemOrder(uint64 poolId, bytes16 trancheId, address destinationAddress, address currency, uint128 trancheTokensPayout, uint128 remainingRedeemOrder) external; + function handleCollectInvest(uint64 poolId, bytes16 trancheId, address destinationAddress, address currency, uint128 currencyInvested, uint128 tokensPayout, uint128 remainingInvestOrder) external; + function handleCollectRedeem(uint64 poolId, bytes16 trancheId, address destinationAddress, address currency, uint128 currencyPayout, uint128 tokensRedeemed, uint128 remainingRedeemOrder) external; } interface RouterLike { @@ -40,6 +44,8 @@ contract ConnectorGateway { // TODO: support multiple incoming routers (just a single outgoing router) to simplify router migrations RouterLike public immutable router; + bool public active; + /// --- Events --- event Rely(address indexed user); event Deny(address indexed user); @@ -48,6 +54,7 @@ contract ConnectorGateway { constructor(address connector_, address router_) { connector = ConnectorLike(connector_); router = RouterLike(router_); + active = true; wards[msg.sender] = 1; emit Rely(msg.sender); @@ -204,7 +211,24 @@ contract ConnectorGateway { (uint64 poolId, bytes16 trancheId, address destinationAddress, uint128 amount) = ConnectorMessages.parseTransferTrancheTokens20(_msg); connector.handleTransferTrancheTokens(poolId, trancheId, destinationAddress, amount); - } else { + } else if (ConnectorMessages.isCollectRedeem(_msg)) { + (uint64 poolId, bytes16 trancheId, address destinationAddress, address currency, uint128 currencyPayout, uint128 tokensRedeemed, uint128 remainingRedeemOrder) = + ConnectorMessages.parseCollectRedeem(_msg); + connector.handleCollectRedeem(poolId, trancheId, destinationAddress, currency, currencyPayout, tokensRedeemed, remainingRedeemOrder); + } else if (ConnectorMessages.isCollectInvest(_msg)) { + (uint64 poolId, bytes16 trancheId, address destinationAddress, address currency, uint128 currencyInvested, uint128 tokensPayout, uint128 remainingInvestOrder) = + ConnectorMessages.parseCollectInvest(_msg); + connector.handleCollectInvest(poolId, trancheId, destinationAddress, currency, currencyInvested, tokensPayout, remainingInvestOrder); + } else if (ConnectorMessages.isDecreaseInvestOrder(_msg)) { + (uint64 poolId, bytes16 trancheId, address destinationAddress, address currency, uint128 currencyPayout, uint128 remainingInvestOrder) = + ConnectorMessages.parseDecreaseInvestOrder(_msg); + connector.handleDecreaseInvestOrder(poolId, trancheId, destinationAddress, currency, currencyPayout, remainingInvestOrder); + } else if (ConnectorMessages.isDecreaseRedeemOrder(_msg)) { + (uint64 poolId, bytes16 trancheId, address destinationAddress, address currency, uint128 trancheTokensPayout, uint128 remainingRedeemOrder) = + ConnectorMessages.parseDecreaseRedeemOrder(_msg); + connector.handleDecreaseRedeemOrder(poolId, trancheId, destinationAddress, currency, trancheTokensPayout, remainingRedeemOrder); + } + else { revert("ConnectorGateway/invalid-message"); } } From 0e1f55db732311a600053d229c5546631fd51aec Mon Sep 17 00:00:00 2001 From: ilinzweilin Date: Mon, 19 Jun 2023 14:59:32 +0100 Subject: [PATCH 03/24] fix error message --- src/Connector.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Connector.sol b/src/Connector.sol index 6783e41c..170103ef 100644 --- a/src/Connector.sol +++ b/src/Connector.sol @@ -108,7 +108,7 @@ contract CentrifugeConnector is Auth { } modifier connectorsActive() { - require(gateway.active(), "CentrifugeConnector/not-the-gateway"); + require(gateway.active(), "CentrifugeConnector/connectors-deactivated"); _; } From 9cda91f84ba4b2b19164b342b94e4076bbf78fe9 Mon Sep 17 00:00:00 2001 From: ilinzweilin Date: Mon, 19 Jun 2023 21:01:24 +0100 Subject: [PATCH 04/24] decrease Order handling + more refactoring --- src/Connector.sol | 169 +++++++++++++++++++++++----------------- src/Messages.sol | 16 ++-- src/Tranche4626.sol | 29 +++---- src/routers/Gateway.sol | 16 ++-- src/token/erc20.sol | 1 - 5 files changed, 124 insertions(+), 107 deletions(-) diff --git a/src/Connector.sol b/src/Connector.sol index 170103ef..3b3bd683 100644 --- a/src/Connector.sol +++ b/src/Connector.sol @@ -37,6 +37,11 @@ interface GatewayLike { function active() external returns(bool); } +interface TrancheLike { + function updateTokenPrice(uint128 _tokenPrice) external; + function asset() external returns (address); +} + interface EscrowLike { function approve(address token, address spender, uint256 value) external; } @@ -47,30 +52,19 @@ struct Pool { bool isActive; } -struct Tranche { - address token; - uint128 latestPrice; // Fixed point integer with 27 decimals - uint256 lastPriceUpdate; - // TODO: the token name & symbol need to be stored because of the separation between adding and deploying tranches. - // This leads to duplicate storage (also in the ERC20 contract), ideally we should r efactor this somehow - string tokenName; - string tokenSymbol; - uint8 decimals; -} - struct UserTrancheValues { uint256 maxDeposit; uint256 maxMint; uint256 maxWithdraw; uint256 maxRedeem; uint256 openRedeem; - uint256 openDeposit; + uint256 openInvest; } contract CentrifugeConnector is Auth { mapping(uint64 => Pool) public pools; - mapping(uint64 => mapping(bytes16 => Tranche)) public tranches; + mapping(uint64 => mapping(bytes16 => address)) public tranches; mapping(address => mapping(address => UserTrancheValues)) public orderbook; // contains outstanding orders and limits for each user and tranche mapping(uint128 => address) public currencyIdToAddress; @@ -127,7 +121,7 @@ contract CentrifugeConnector is Auth { // --- Outgoing message handling --- // auth functions - function deposit(uint64 _poolId, address _tranche, address _receiver, uint256 _assets) public poolActive(_poolId) connectorsActive auth returns (uint256) { + function deposit(address _tranche, address _receiver, uint256 _assets) public poolActive(_poolId) connectorsActive auth returns (uint256) { require((_assets <= orderbook[_receiver][_tranche].maxDeposit), "CentrifugeConnector/amount-exceeds-deposit-limits"); uint256 sharePrice = calcUserSharePrice( _receiver, _tranche); require((sharePrice > 0), "Tranche4626/amount-exceeds-deposit-limits"); @@ -138,7 +132,7 @@ contract CentrifugeConnector is Auth { return sharesToTransfer; } - function mint(uint64 _poolId, address _tranche, address _receiver, uint256 _shares) public poolActive(_poolId) connectorsActive auth returns (uint256) { + function mint(address _tranche, address _receiver, uint256 _shares) public poolActive(_poolId) connectorsActive auth returns (uint256) { require((_shares <= orderbook[_receiver][_tranche].maxMint), "CentrifugeConnector/amount-exceeds-mint-limits"); uint256 sharePrice = calcUserSharePrice( _receiver, _tranche); require((sharePrice > 0), "Tranche4626/amount-exceeds-deposit-limits"); @@ -149,7 +143,6 @@ contract CentrifugeConnector is Auth { return requiredDeposit; } - function transfer(address currencyAddress, bytes32 recipient, uint128 amount) public { uint128 currency = currencyAddressToId[currencyAddress]; require(currency != 0, "CentrifugeConnector/unknown-currency"); @@ -167,7 +160,7 @@ contract CentrifugeConnector is Auth { bytes32 destinationAddress, uint128 amount ) public { - RestrictedTokenLike token = RestrictedTokenLike(tranches[poolId][trancheId].token); + RestrictedTokenLike token = RestrictedTokenLike(tranches[poolId][trancheId]); require(address(token) != address(0), "CentrifugeConnector/unknown-token"); require(token.balanceOf(msg.sender) >= amount, "CentrifugeConnector/insufficient-balance"); @@ -183,7 +176,7 @@ contract CentrifugeConnector is Auth { address destinationAddress, uint128 amount ) public { - RestrictedTokenLike token = RestrictedTokenLike(tranches[poolId][trancheId].token); + RestrictedTokenLike token = RestrictedTokenLike(tranches[poolId][trancheId]); require(address(token) != address(0), "CentrifugeConnector/unknown-token"); require(token.balanceOf(msg.sender) >= amount, "CentrifugeConnector/insufficient-balance"); @@ -195,7 +188,7 @@ contract CentrifugeConnector is Auth { } function increaseInvestOrder(uint64 poolId, bytes16 trancheId, address currencyAddress, uint128 amount) public { - RestrictedTokenLike token = RestrictedTokenLike(tranches[poolId][trancheId].token); + RestrictedTokenLike token = RestrictedTokenLike(tranches[poolId][trancheId]); require(address(token) != address(0), "CentrifugeConnector/unknown-tranche-token"); require(token.hasMember(msg.sender), "CentrifugeConnector/not-a-member"); @@ -224,7 +217,7 @@ contract CentrifugeConnector is Auth { } function increaseRedeemOrder(uint64 poolId, bytes16 trancheId, address currencyAddress, uint128 amount) public { - RestrictedTokenLike token = RestrictedTokenLike(tranches[poolId][trancheId].token); + RestrictedTokenLike token = RestrictedTokenLike(tranches[poolId][trancheId]); require(address(token) != address(0), "CentrifugeConnector/unknown-tranche-token"); require(token.hasMember(msg.sender), "CentrifugeConnector/not-a-member"); @@ -236,7 +229,7 @@ contract CentrifugeConnector is Auth { } function decreaseRedeemOrder(uint64 poolId, bytes16 trancheId, address currencyAddress, uint128 amount) public { - RestrictedTokenLike token = RestrictedTokenLike(tranches[poolId][trancheId].token); + RestrictedTokenLike token = RestrictedTokenLike(tranches[poolId][trancheId]); require(address(token) != address(0), "CentrifugeConnector/unknown-tranche-token"); require(token.hasMember(msg.sender), "CentrifugeConnector/not-a-member"); @@ -248,7 +241,7 @@ contract CentrifugeConnector is Auth { } function collectInvest(uint64 poolId, bytes16 trancheId) public { - RestrictedTokenLike token = RestrictedTokenLike(tranches[poolId][trancheId].token); + RestrictedTokenLike token = RestrictedTokenLike(tranches[poolId][trancheId]); require(address(token) != address(0), "CentrifugeConnector/unknown-tranche-token"); require(token.hasMember(msg.sender), "CentrifugeConnector/not-a-member"); @@ -256,7 +249,7 @@ contract CentrifugeConnector is Auth { } function collectRedeem(uint64 poolId, bytes16 trancheId) public { - RestrictedTokenLike token = RestrictedTokenLike(tranches[poolId][trancheId].token); + RestrictedTokenLike token = RestrictedTokenLike(tranches[poolId][trancheId]); require(address(token) != address(0), "CentrifugeConnector/unknown-tranche-token"); require(token.hasMember(msg.sender), "CentrifugeConnector/not-a-member"); @@ -294,54 +287,37 @@ contract CentrifugeConnector is Auth { } function addTranche( - uint64 poolId, - bytes16 trancheId, - string memory tokenName, - string memory tokenSymbol, - uint8 decimals, - uint128 price + uint64 _poolId, + bytes16 _trancheId, + string memory _tokenName, + string memory _tokenSymbol, + uint8 _decimals, + uint128 _price ) public onlyGateway { - Pool storage pool = pools[poolId]; + Pool storage pool = pools[_poolId]; require(pool.createdAt > 0, "CentrifugeConnector/invalid-pool"); - Tranche storage tranche = tranches[poolId][trancheId]; - require(tranche.lastPriceUpdate == 0, "CentrifugeConnector/tranche-already-added"); - tranche.latestPrice = price; - tranche.lastPriceUpdate = block.timestamp; - tranche.tokenName = tokenName; - tranche.tokenSymbol = tokenSymbol; - tranche.decimals = decimals; + address token = tranches[_poolId][_trancheId]; + require(token == address(0), "CentrifugeConnector/tranche-already-added"); + address asset = address(0x6B175474E89094C44Da98b954EedeAC495271d0F); // TODO FIX : provide tranche currency / assets : default DAI? + token = deployTranche(poolId, trancheId, asset, tokenName, tokenSymbol, decimals); + + TrancheLike(token).updateTokenPrice(_price); emit TrancheAdded(poolId, trancheId); } - function deployTranche(uint64 poolId, bytes16 trancheId) public { - Tranche storage tranche = tranches[poolId][trancheId]; - require(tranche.lastPriceUpdate > 0, "CentrifugeConnector/invalid-pool-or-tranche"); - require(tranche.token == address(0), "CentrifugeConnector/tranche-already-deployed"); - - address token = - tokenFactory.newTrancheToken(poolId, trancheId, tranche.tokenName, tranche.tokenSymbol, tranche.decimals); - tranche.token = token; - - address memberlist = memberlistFactory.newMemberlist(); - RestrictedTokenLike(token).file("memberlist", memberlist); - MemberlistLike(memberlist).updateMember(address(this), type(uint256).max); // required to be able to receive tokens in case of withdrawals - emit TrancheDeployed(poolId, trancheId, token); - } - - function updateTokenPrice(uint64 poolId, bytes16 trancheId, uint128 price) public onlyGateway { - Tranche storage tranche = tranches[poolId][trancheId]; - require(tranche.lastPriceUpdate > 0, "CentrifugeConnector/invalid-pool-or-tranche"); - tranche.latestPrice = price; - tranche.lastPriceUpdate = block.timestamp; + function updateTokenPrice(uint64 _poolId, bytes16 _trancheId, uint128 _price) public onlyGateway { + address token = tranches[_poolId][_trancheId]; + require(token != address(0), "CentrifugeConnector/invalid-pool-or-tranche"); + TrancheLike(token).updateTokenPrice(_price); } function updateMember(uint64 poolId, bytes16 trancheId, address user, uint64 validUntil) public onlyGateway { - Tranche storage tranche = tranches[poolId][trancheId]; - require(tranche.lastPriceUpdate > 0, "CentrifugeConnector/invalid-pool-or-tranche"); - RestrictedTokenLike token = RestrictedTokenLike(tranche.token); - MemberlistLike memberlist = MemberlistLike(token.memberlist()); + address token = tranches[_poolId][_trancheId]; + require(token != address(0), "CentrifugeConnector/invalid-pool-or-tranche"); + RestrictedTokenLike trancheToken = RestrictedTokenLike(tranche.token); + MemberlistLike memberlist = RestrictedTokenLike(token).memberlist(); memberlist.updateMember(user, validUntil); } @@ -360,22 +336,52 @@ contract CentrifugeConnector is Auth { public onlyGateway { - RestrictedTokenLike token = RestrictedTokenLike(tranches[poolId][trancheId].token); + RestrictedTokenLike token = RestrictedTokenLike(tranches[poolId][trancheId]); require(address(token) != address(0), "CentrifugeConnector/unknown-token"); require(token.hasMember(destinationAddress), "CentrifugeConnector/not-a-member"); token.mint(destinationAddress, amount); } - function handleDecreaseInvestOrder(uint64 poolId, bytes16 trancheId, address destinationAddress, address currency, uint128 currencyPayout, uint128 remainingInvestOrder) public onlyGateway {}; - function handleDecreaseRedeemOrder(uint64 poolId, bytes16 trancheId, address destinationAddress, address currency, uint128 trancheTokensPayout, uint128 remainingRedeemOrder) public onlyGateway {}; - function handleCollectInvest(uint64 poolId, bytes16 trancheId, address destinationAddress, address currency, uint128 currencyInvested, uint128 tokensPayout, uint128 remainingInvestOrder) public onlyGateway {}; - function handleCollectRedeem(uint64 poolId, bytes16 trancheId, address destinationAddress, address currency, uint128 currencyPayout, uint128 tokensRedeemed, uint128 remainingRedeemOrder) public onlyGateway {}; + function handleDecreaseInvestOrder(uint64 _poolId, bytes16 _trancheId, address _recipient, uint128 _currency, uint128 _currencyPayout, uint128 _remainingInvestOrder) public onlyGateway { + require(_currencyPayout != 0, "CentrifugeConnector/zero-payout"); + address currencyAddress = currencyIdToAddress[_currency]; + address tranche = tranches[_poolId][_trancheId]; + require(allowedPoolCurrencies[_poolId][currencyAddress], "CentrifugeConnector/pool-currency-not-allowed"); + require(currencyAddress != address(0), "CentrifugeConnector/unknown-currency"); + require(currencyAddress == TrancheLike(tranche).asset(), "CentrifugeConnector/not-tranche-currency"); - // ------ EIP 4626 helper functions + // TODO: escrow should give max approval on deployment + EscrowLike(escrow).approve(currencyAddress, address(this), _currencyPayout); + + require( + ERC20Like(currencyAddress).transferFrom(address(escrow), _recipient, _currencyPayout), + "CentrifugeConnector/currency-transfer-failed" + ); + orderbook[_recipient][tranche].openInvest = _remainingInvestOrder; + } - // function decreaseRedemptionsLimit(uint256 _assets, uint256 _shares) public auth {} + //TODO: currency not really required here + function handleDecreaseRedeemOrder(uint64 _poolId, bytes16 _trancheId, address _recipient, uint128 _currency, uint128 _trancheTokensPayout, uint128 _remainingRedeemOrder) public onlyGateway { + require(_trancheTokensPayout != 0, "CentrifugeConnector/zero-payout"); + address tranche = tranches[poolId][trancheId]; + + require(RestrictedTokenLike(tranche).hasMember(_recipient), "CentrifugeConnector/not-a-member"); + // TODO: escrow should give max approval on deployment + EscrowLike(escrow).approve(tranche, address(this), _trancheTokensPayout); + require( + ERC20Like(tranche).transferFrom(address(escrow), _recipient, _trancheTokensPayout), + "CentrifugeConnector/trancheTokens-transfer-failed" + ); + orderbook[_recipient][tranche].openRedeem = _remainingRedeemOrder; + } + + function handleCollectInvest(uint64 poolId, bytes16 trancheId, address destinationAddress, uint128 currency, uint128 currencyInvested, uint128 tokensPayout, uint128 remainingInvestOrder) public onlyGateway {}; + function handleCollectRedeem(uint64 poolId, bytes16 trancheId, address destinationAddress, uint128 currency, uint128 currencyPayout, uint128 tokensRedeemed, uint128 remainingRedeemOrder) public onlyGateway {}; + + + // ------ internal helper functions //TODO: rounding function decreaseDepositLimits(address _user, address _tranche, uint256 _assets, uint256 _shares) internal { @@ -385,7 +391,6 @@ contract CentrifugeConnector is Auth { } else { values.maxDeposit = values.maxDeposit - _assets; } - if (values.maxMint < _shares) { values.maxMint = 0; } else { @@ -393,13 +398,37 @@ contract CentrifugeConnector is Auth { } } + // function decreaseRedemptionsLimit(uint256 _assets, uint256 _shares) public auth {} + + + // TODO: add decimals to constructor of restricted token + // TODO: ward setup on tranche contract + function deployTranche( + uint64 _poolId, + bytes16 _trancheId, + address _asset, + uint8 _decimals, + string memory _tokenName, + string memory _tokenSymbol) internal returns (address) { + require(tranches[_poolId][_trancheId] == address(0), "CentrifugeConnector/tranche-already-deployed"); + address token = deployTranche(_asset, address(this), _decimals, _tokenName, _tokenSymbol); // TODO: use factory + tranches[_poolId][_trancheId] = token; + + address memberlist = memberlistFactory.newMemberlist(); + RestrictedTokenLike(token).file("memberlist", memberlist); + MemberlistLike(memberlist).updateMember(address(escrow), type(uint256).max); // add escrow to tranche tokens memberlist + emit TrancheDeployed(poolId, trancheId, token); + } + + // ------ EIP 4626 view functions + /// @dev calculates the avg share price for the deposited assets of a specific user function calcUserSharePrice(address _user, address _tranche) public view returns (uint256 sharePrice) { UserTrancheValues values = orderbook[_user][_tranche]; if(values.maxMint == 0) { return 0; } - sharePrice = values.maxDeposit / orderbook[_user].maxMint; + sharePrice = values.maxDeposit / values.maxMint; } function maxDeposit(address _user, address _tranche) public view returns (uint256) { diff --git a/src/Messages.sol b/src/Messages.sol index 51b5a99b..3fa2b475 100644 --- a/src/Messages.sol +++ b/src/Messages.sol @@ -364,42 +364,42 @@ library ConnectorMessages { // function parseCollectRedeem(bytes29 _msg) // internal // pure -// returns (uint64 poolId, bytes16 trancheId, address destinationAddress, address currency, uint128 currencyPayout, uint128 tokensRedeemed, uint128 remainingRedeemOrder) {} +// returns (uint64 poolId, bytes16 trancheId, address destinationAddress, uint128 currency, uint128 currencyPayout, uint128 tokensRedeemed, uint128 remainingRedeemOrder) {} // function isCollectRedeem(bytes29 _msg) // internal // pure -// returns (uint64 poolId, bytes16 trancheId, address destinationAddress, address currency, uint128 currencyPayout, uint128 tokensRedeemed, uint128 remainingRedeemOrder) {} +// returns (uint64 poolId, bytes16 trancheId, address destinationAddress, uint128 currency, uint128 currencyPayout, uint128 tokensRedeemed, uint128 remainingRedeemOrder) {} // function parseCollectInvest(bytes29 _msg) // internal // pure -// returns (uint64 poolId, bytes16 trancheId, address destinationAddress, address currency, uint128 currencyInvested, uint128 tokensPayout, uint128 remainingInvestOrder){} +// returns (uint64 poolId, bytes16 trancheId, address destinationAddress, uint128 currency, uint128 currencyInvested, uint128 tokensPayout, uint128 remainingInvestOrder){} // function isCollectInvest(bytes29 _msg) // internal // pure -// returns (uint64 poolId, bytes16 trancheId, address destinationAddress, address currency, uint128 currencyInvested, uint128 tokensPayout, uint128 remainingInvestOrder){} +// returns (uint64 poolId, bytes16 trancheId, address destinationAddress, uint128 currency, uint128 currencyInvested, uint128 tokensPayout, uint128 remainingInvestOrder){} // function parseDecreaseInvestOrder(bytes29 _msg) // internal // pure -// returns (uint64 poolId, bytes16 trancheId, address destinationAddress, address currency, uint128 currencyPayout, uint128 remainingInvestOrder){} +// returns (uint64 poolId, bytes16 trancheId, address destinationAddress, uint128 currency, uint128 currencyPayout, uint128 remainingInvestOrder){} // function isDecreaseInvestOrder(bytes29 _msg) // internal // pure -// returns (uint64 poolId, bytes16 trancheId, address destinationAddress, address currency, uint128 currencyPayout, uint128 remainingInvestOrder){} +// returns (uint64 poolId, bytes16 trancheId, address destinationAddress, uint128 currency, uint128 currencyPayout, uint128 remainingInvestOrder){} // function parseDecreaseRedeemOrder(bytes29 _msg) // internal // pure -// returns (uint64 poolId, bytes16 trancheId, address destinationAddress, address currency, uint128 currencyPayout, uint128 remainingInvestOrder){} +// returns (uint64 poolId, bytes16 trancheId, address destinationAddress, uint128 currency, uint128 currencyPayout, uint128 remainingInvestOrder){} // function isDecreaseRedeemOrder(bytes29 _msg) // internal // pure -// returns (uint64 poolId, bytes16 trancheId, address destinationAddress, address currency, uint128 currencyPayout, uint128 remainingInvestOrder){} +// returns (uint64 poolId, bytes16 trancheId, address destinationAddress, uint128 currency, uint128 currencyPayout, uint128 remainingInvestOrder){} /* * IncreaseInvestOrder Message diff --git a/src/Tranche4626.sol b/src/Tranche4626.sol index eab7dbf1..c9906de7 100644 --- a/src/Tranche4626.sol +++ b/src/Tranche4626.sol @@ -31,8 +31,8 @@ pragma solidity ^0.8.18; import "./token/restricted.sol"; interface ConnectorLike { - function deposit(uint64 _poolId, address _tranche, address _receiver, uint256 _assets) external returns (uint256); - function mint(uint64 _poolId, address _tranche, address _receiver, uint256 _shares) external returns (uint256); + function deposit(address _tranche, address _receiver, uint256 _assets) external returns (uint256); + function mint(address _tranche, address _receiver, uint256 _shares) external returns (uint256); function maxDeposit(address _user, address _tranche) external returns (uint256); function maxMint(address _user, address _tranche) external returns (uint256); function maxWithdraw(address _user, address _tranche) external returns (uint256); @@ -47,8 +47,6 @@ contract Tranche4626 is RestrictedToken { address public asset; // underlying stable ERC-20 stable currency. uint256 public maxAssetDeposit = 2 ** 256 - 1; // max stable currency deposit into the tranche -> default: no limit. - uint64 public poolId; // the id of the Centrifuge pool the Tranche belongs to. - bytes16 trancheId; // the trancheId valid across all chains. uint128 latestPrice; // lates share / token price uint256 lastPriceUpdate; // timestamp of the latest share / token price update @@ -57,13 +55,13 @@ contract Tranche4626 is RestrictedToken { event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares); event Withdraw(address indexed sender, address indexed receiver, address indexed owner, uint256 assets, uint256 shares); - constructor(address _asset, bytes16 _trancheId, uint64 _poolId, address _connector) { + constructor(address _asset, address _connector, uint8 _decimals, string memory _name, string memory _symbol) super(decimals) { + name = _name; + symbol = _symbol; asset = _asset; - trancheId = _trancheId; - poolId = _poolId; connector = ConnectorLike(_connector); - wards[msg.sender] = 1; + wards[_connector] = 1; emit Rely(msg.sender); } @@ -102,14 +100,14 @@ contract Tranche4626 is RestrictedToken { /// @dev collect shares for deposited funds after pool epoch execution. maxMint is the max amount of shares that can be collected. Required assets must already be locked. /// maxDeposit is the amount of funds that was successfully invested into the pool on Centrifuge chain function deposit(uint256 _assets, address _receiver) auth public returns (uint256 shares) { - uint transferredShares = connector.deposit(poolId, address(this), _receiver, _assets); + uint transferredShares = connector.deposit(address(this), _receiver, _assets); Deposit(address(this), _receiver, _assets, transferredShares); } /// @dev collect shares for deposited funds after pool epoch execution. maxMint is the max amount of shares that can be collected. Required assets must already be locked. /// maxDeposit is the amount of funds that was successfully invested into the pool on Centrifuge chain function mint(uint256 _shares, address _receiver) auth public returns (uint256 assets) { - uint lockedAssets = connector.mint(poolId, address(this), _receiver, _shares); + uint lockedAssets = connector.mint(address(this), _receiver, _shares); Deposit(address(this), _receiver, lockedAssets, _shares); } @@ -139,7 +137,7 @@ contract Tranche4626 is RestrictedToken { function maxRedeem(address owner) external view returns (uint256 maxShares) { return connector.maxRedeem(_owner, address(this)); } - + /// @dev /// @return function previewRedeem(uint256 shares) external view returns (uint256 assets); @@ -151,13 +149,4 @@ contract Tranche4626 is RestrictedToken { latestPrice = _tokenPrice; lastPriceUpdate = block.timestamp; } - - function updateTotalAssets(uint128 _tokenPrice) public auth { - latestPrice = _tokenPrice; - lastPriceUpdate = block.timestamp; - } - - - - } diff --git a/src/routers/Gateway.sol b/src/routers/Gateway.sol index bb2efac1..b021520c 100644 --- a/src/routers/Gateway.sol +++ b/src/routers/Gateway.sol @@ -22,10 +22,10 @@ interface ConnectorLike { function handleTransfer(uint128 currency, address recipient, uint128 amount) external; function handleTransferTrancheTokens(uint64 poolId, bytes16 trancheId, address destinationAddress, uint128 amount) external; - function handleDecreaseInvestOrder(uint64 poolId, bytes16 trancheId, address destinationAddress, address currency, uint128 currencyPayout, uint128 remainingInvestOrder) external; - function handleDecreaseRedeemOrder(uint64 poolId, bytes16 trancheId, address destinationAddress, address currency, uint128 trancheTokensPayout, uint128 remainingRedeemOrder) external; - function handleCollectInvest(uint64 poolId, bytes16 trancheId, address destinationAddress, address currency, uint128 currencyInvested, uint128 tokensPayout, uint128 remainingInvestOrder) external; - function handleCollectRedeem(uint64 poolId, bytes16 trancheId, address destinationAddress, address currency, uint128 currencyPayout, uint128 tokensRedeemed, uint128 remainingRedeemOrder) external; + function handleDecreaseInvestOrder(uint64 poolId, bytes16 trancheId, address destinationAddress, uint128 currency, uint128 currencyPayout, uint128 remainingInvestOrder) external; + function handleDecreaseRedeemOrder(uint64 poolId, bytes16 trancheId, address destinationAddress, uint128 currency, uint128 trancheTokensPayout, uint128 remainingRedeemOrder) external; + function handleCollectInvest(uint64 poolId, bytes16 trancheId, address destinationAddress, uint128 currency, uint128 currencyInvested, uint128 tokensPayout, uint128 remainingInvestOrder) external; + function handleCollectRedeem(uint64 poolId, bytes16 trancheId, address destinationAddress, uint128 currency, uint128 currencyPayout, uint128 tokensRedeemed, uint128 remainingRedeemOrder) external; } interface RouterLike { @@ -212,19 +212,19 @@ contract ConnectorGateway { ConnectorMessages.parseTransferTrancheTokens20(_msg); connector.handleTransferTrancheTokens(poolId, trancheId, destinationAddress, amount); } else if (ConnectorMessages.isCollectRedeem(_msg)) { - (uint64 poolId, bytes16 trancheId, address destinationAddress, address currency, uint128 currencyPayout, uint128 tokensRedeemed, uint128 remainingRedeemOrder) = + (uint64 poolId, bytes16 trancheId, address destinationAddress, uint128 currency, uint128 currencyPayout, uint128 tokensRedeemed, uint128 remainingRedeemOrder) = ConnectorMessages.parseCollectRedeem(_msg); connector.handleCollectRedeem(poolId, trancheId, destinationAddress, currency, currencyPayout, tokensRedeemed, remainingRedeemOrder); } else if (ConnectorMessages.isCollectInvest(_msg)) { - (uint64 poolId, bytes16 trancheId, address destinationAddress, address currency, uint128 currencyInvested, uint128 tokensPayout, uint128 remainingInvestOrder) = + (uint64 poolId, bytes16 trancheId, address destinationAddress, uint128 currency, uint128 currencyInvested, uint128 tokensPayout, uint128 remainingInvestOrder) = ConnectorMessages.parseCollectInvest(_msg); connector.handleCollectInvest(poolId, trancheId, destinationAddress, currency, currencyInvested, tokensPayout, remainingInvestOrder); } else if (ConnectorMessages.isDecreaseInvestOrder(_msg)) { - (uint64 poolId, bytes16 trancheId, address destinationAddress, address currency, uint128 currencyPayout, uint128 remainingInvestOrder) = + (uint64 poolId, bytes16 trancheId, address destinationAddress, uint128 currency, uint128 currencyPayout, uint128 remainingInvestOrder) = ConnectorMessages.parseDecreaseInvestOrder(_msg); connector.handleDecreaseInvestOrder(poolId, trancheId, destinationAddress, currency, currencyPayout, remainingInvestOrder); } else if (ConnectorMessages.isDecreaseRedeemOrder(_msg)) { - (uint64 poolId, bytes16 trancheId, address destinationAddress, address currency, uint128 trancheTokensPayout, uint128 remainingRedeemOrder) = + (uint64 poolId, bytes16 trancheId, address destinationAddress, uint128 currency, uint128 trancheTokensPayout, uint128 remainingRedeemOrder) = ConnectorMessages.parseDecreaseRedeemOrder(_msg); connector.handleDecreaseRedeemOrder(poolId, trancheId, destinationAddress, currency, trancheTokensPayout, remainingRedeemOrder); } diff --git a/src/token/erc20.sol b/src/token/erc20.sol index 0a3d4190..faef2681 100644 --- a/src/token/erc20.sol +++ b/src/token/erc20.sol @@ -11,7 +11,6 @@ interface IERC1271 { // Adapted from https://github.com/makerdao/xdomain-dss/blob/master/src/Dai.sol contract ERC20 is Auth { - string public name; string public symbol; string public constant version = "3"; From a7d484540c7524d7b3aa127343e38d52c92a6598 Mon Sep 17 00:00:00 2001 From: ilinzweilin Date: Mon, 19 Jun 2023 21:05:26 +0100 Subject: [PATCH 05/24] more refactoring --- src/Connector.sol | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Connector.sol b/src/Connector.sol index 3b3bd683..2a82f518 100644 --- a/src/Connector.sol +++ b/src/Connector.sol @@ -127,8 +127,7 @@ contract CentrifugeConnector is Auth { require((sharePrice > 0), "Tranche4626/amount-exceeds-deposit-limits"); uint256 sharesToTransfer = _assets / sharePrice; decreaseDepositLimits(_receiver, _tranche, _assets, sharesToTransfer); // decrease the possible deposit limits - ERC20Like erc20 = ERC20Like(_tranche); - require(erc20.transferFrom(address(escrow), _receiver, sharesToTransfer), "CentrifugeConnector/shares-transfer-failed"); + require(ERC20Like(_tranche).transferFrom(address(escrow), _receiver, sharesToTransfer), "CentrifugeConnector/shares-transfer-failed"); return sharesToTransfer; } @@ -138,8 +137,7 @@ contract CentrifugeConnector is Auth { require((sharePrice > 0), "Tranche4626/amount-exceeds-deposit-limits"); uint256 requiredDeposit = _shares * sharePrice; decreaseDepositLimits(_receiver, _tranche, requiredDeposit, _shares); // decrease the possible deposit limits - ERC20Like erc20 = ERC20Like(_tranche); - require(erc20.transferFrom(address(escrow), _receiver, _shares), "CentrifugeConnector/shares-transfer-failed"); + require(ERC20Like(_tranche).transferFrom(address(escrow), _receiver, _shares), "CentrifugeConnector/shares-transfer-failed"); return requiredDeposit; } @@ -376,7 +374,7 @@ contract CentrifugeConnector is Auth { ); orderbook[_recipient][tranche].openRedeem = _remainingRedeemOrder; } - + function handleCollectInvest(uint64 poolId, bytes16 trancheId, address destinationAddress, uint128 currency, uint128 currencyInvested, uint128 tokensPayout, uint128 remainingInvestOrder) public onlyGateway {}; function handleCollectRedeem(uint64 poolId, bytes16 trancheId, address destinationAddress, uint128 currency, uint128 currencyPayout, uint128 tokensRedeemed, uint128 remainingRedeemOrder) public onlyGateway {}; From 9afdcffdd0d4129b4ee29b0e9f57a867cb4e4bd7 Mon Sep 17 00:00:00 2001 From: ilinzweilin Date: Tue, 20 Jun 2023 13:40:54 +0100 Subject: [PATCH 06/24] add handle collectRedeem/Invest --- src/Connector.sol | 41 ++++++++++++++++++++++++++++++++++------- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/src/Connector.sol b/src/Connector.sol index 2a82f518..22296ef0 100644 --- a/src/Connector.sol +++ b/src/Connector.sol @@ -37,9 +37,11 @@ interface GatewayLike { function active() external returns(bool); } +// is RestrictedToken interface TrancheLike { function updateTokenPrice(uint128 _tokenPrice) external; function asset() external returns (address); + function mint(address, uint) external; } interface EscrowLike { @@ -345,7 +347,7 @@ contract CentrifugeConnector is Auth { require(_currencyPayout != 0, "CentrifugeConnector/zero-payout"); address currencyAddress = currencyIdToAddress[_currency]; address tranche = tranches[_poolId][_trancheId]; - + require(tranche != address(0), "CentrifugeConnector/tranche-does-not-exist"); require(allowedPoolCurrencies[_poolId][currencyAddress], "CentrifugeConnector/pool-currency-not-allowed"); require(currencyAddress != address(0), "CentrifugeConnector/unknown-currency"); require(currencyAddress == TrancheLike(tranche).asset(), "CentrifugeConnector/not-tranche-currency"); @@ -361,22 +363,47 @@ contract CentrifugeConnector is Auth { } //TODO: currency not really required here - function handleDecreaseRedeemOrder(uint64 _poolId, bytes16 _trancheId, address _recipient, uint128 _currency, uint128 _trancheTokensPayout, uint128 _remainingRedeemOrder) public onlyGateway { - require(_trancheTokensPayout != 0, "CentrifugeConnector/zero-payout"); + function handleDecreaseRedeemOrder(uint64 _poolId, bytes16 _trancheId, address _recipient, uint128 _currency, uint128 _tokensPayout, uint128 _remainingRedeemOrder) public onlyGateway { + require(_tokensPayout != 0, "CentrifugeConnector/zero-payout"); address tranche = tranches[poolId][trancheId]; + require(tranche != address(0), "CentrifugeConnector/tranche-does-not-exist"); require(RestrictedTokenLike(tranche).hasMember(_recipient), "CentrifugeConnector/not-a-member"); // TODO: escrow should give max approval on deployment - EscrowLike(escrow).approve(tranche, address(this), _trancheTokensPayout); + EscrowLike(escrow).approve(tranche, address(this), _tokensPayout); require( - ERC20Like(tranche).transferFrom(address(escrow), _recipient, _trancheTokensPayout), + ERC20Like(tranche).transferFrom(address(escrow), _recipient, _tokensPayout), "CentrifugeConnector/trancheTokens-transfer-failed" ); orderbook[_recipient][tranche].openRedeem = _remainingRedeemOrder; } - function handleCollectInvest(uint64 poolId, bytes16 trancheId, address destinationAddress, uint128 currency, uint128 currencyInvested, uint128 tokensPayout, uint128 remainingInvestOrder) public onlyGateway {}; - function handleCollectRedeem(uint64 poolId, bytes16 trancheId, address destinationAddress, uint128 currency, uint128 currencyPayout, uint128 tokensRedeemed, uint128 remainingRedeemOrder) public onlyGateway {}; + function handleCollectInvest(uint64 _poolId, bytes16 _trancheId, address _recepient, uint128 _currency, uint128 _currencyInvested, uint128 _tokensPayout, uint128 _remainingInvestOrder) public onlyGateway { + require(_currencyInvested != 0, "CentrifugeConnector/zero-invest"); + address tranche = tranches[poolId][trancheId]; + require(tranche != address(0), "CentrifugeConnector/tranche-does-not-exist"); + + UserTrancheValues values = orderbook[_recepient][_tranche]; + values.openInvest = _remainingInvestOrder; + values.maxDeposit = values.maxDeposit + _currencyInvested; + values.maxMint = values.maxMint + _tokensPayout; + + TrancheLike(tranche).mint(address(escrow), _tokensPayout); // mint to escrow. Recepeint can claim by calling withdraw / redeem + } + + function handleCollectRedeem(uint64 _poolId, bytes16 _trancheId, address _recepient, uint128 _currency, uint128 _currencyPayout, uint128 _tokensRedeemed, uint128 _remainingRedeemOrder) public onlyGateway { + require(_tokensRedeemed != 0, "CentrifugeConnector/zero-redeem"); + address tranche = tranches[poolId][trancheId]; + require(tranche != address(0), "CentrifugeConnector/tranche-does-not-exist"); + + UserTrancheValues values = orderbook[_recepient][_tranche]; + values.openRedeem = _remainingRedeemOrder; + values.maxWithdraw = values.maxWithdraw + _currencyPayout; + values.maxRedeem = values.maxRedeem + _tokensRedeemed; + + + + } // ------ internal helper functions From 20b94f91f121035f55846bbcdcc233562e958d78 Mon Sep 17 00:00:00 2001 From: ilinzweilin Date: Tue, 20 Jun 2023 20:29:34 +0100 Subject: [PATCH 07/24] add requestDeposit --- src/Connector.sol | 122 ++++++++++++++++++++++++++++++++------------ src/Tranche4626.sol | 21 ++++---- 2 files changed, 101 insertions(+), 42 deletions(-) diff --git a/src/Connector.sol b/src/Connector.sol index 22296ef0..4bec653f 100644 --- a/src/Connector.sol +++ b/src/Connector.sol @@ -54,6 +54,11 @@ struct Pool { bool isActive; } +struct CFGTranche { + uint64 poolId; + bytes16 trancheId; +} + struct UserTrancheValues { uint256 maxDeposit; uint256 maxMint; @@ -67,6 +72,7 @@ contract CentrifugeConnector is Auth { mapping(uint64 => Pool) public pools; mapping(uint64 => mapping(bytes16 => address)) public tranches; + mapping(address => CFGTranche) public cfgTranches; // maps evm tranches to CFG tranches mapping(address => mapping(address => UserTrancheValues)) public orderbook; // contains outstanding orders and limits for each user and tranche mapping(uint128 => address) public currencyIdToAddress; @@ -121,28 +127,60 @@ contract CentrifugeConnector is Auth { } // --- Outgoing message handling --- - // auth functions - function deposit(address _tranche, address _receiver, uint256 _assets) public poolActive(_poolId) connectorsActive auth returns (uint256) { - require((_assets <= orderbook[_receiver][_tranche].maxDeposit), "CentrifugeConnector/amount-exceeds-deposit-limits"); - uint256 sharePrice = calcUserSharePrice( _receiver, _tranche); + function deposit(address _tranche, address _user, uint256 _assets) public poolActive(_poolId) connectorsActive auth returns (uint256) { + require((_assets <= orderbook[_user][_tranche].maxDeposit), "CentrifugeConnector/amount-exceeds-deposit-limits"); + uint256 sharePrice = calcUserSharePrice( _user, _tranche); require((sharePrice > 0), "Tranche4626/amount-exceeds-deposit-limits"); uint256 sharesToTransfer = _assets / sharePrice; - decreaseDepositLimits(_receiver, _tranche, _assets, sharesToTransfer); // decrease the possible deposit limits - require(ERC20Like(_tranche).transferFrom(address(escrow), _receiver, sharesToTransfer), "CentrifugeConnector/shares-transfer-failed"); + decreaseDepositLimits(_user, _tranche, _assets, sharesToTransfer); // decrease the possible deposit limits + require(ERC20Like(_tranche).transferFrom(address(escrow), _user, sharesToTransfer), "CentrifugeConnector/shares-transfer-failed"); return sharesToTransfer; } - function mint(address _tranche, address _receiver, uint256 _shares) public poolActive(_poolId) connectorsActive auth returns (uint256) { - require((_shares <= orderbook[_receiver][_tranche].maxMint), "CentrifugeConnector/amount-exceeds-mint-limits"); - uint256 sharePrice = calcUserSharePrice( _receiver, _tranche); + function mint(address _tranche, address _user, uint256 _shares) public poolActive(_poolId) connectorsActive auth returns (uint256) { + require((_shares <= orderbook[_user][_tranche].maxMint), "CentrifugeConnector/amount-exceeds-mint-limits"); + uint256 sharePrice = calcUserSharePrice( _user, _tranche); require((sharePrice > 0), "Tranche4626/amount-exceeds-deposit-limits"); uint256 requiredDeposit = _shares * sharePrice; - decreaseDepositLimits(_receiver, _tranche, requiredDeposit, _shares); // decrease the possible deposit limits - require(ERC20Like(_tranche).transferFrom(address(escrow), _receiver, _shares), "CentrifugeConnector/shares-transfer-failed"); + decreaseDepositLimits(_user, _tranche, requiredDeposit, _shares); // decrease the possible deposit limits + require(ERC20Like(_tranche).transferFrom(address(escrow), _user, _shares), "CentrifugeConnector/shares-transfer-failed"); return requiredDeposit; } + function requestRedeem(address _tranche, uint256 _shares, address _user) connectorsActive poolActive(_poolId) auth {} + + // TODO: fix uint256 - uint128 + function requestDeposit(address _tranche, uint256 _assets, address _user) connectorsActive poolActive(_poolId) auth { + UserValues userValues = orderbook[_user][_tranche]; + CFGTranche cfgTranche = cfgTranches[_tranche]; + ERC20Like currency = ERC20Like(TrancheLike(_tranche).asset()); + if (userValues.openRedeem > 0) { // cancel outstanding redeem orders + _decreaseRedeemOrder(cfgTranche.poolId, cfgTranche.trancheId, userValues.openRedeem, _user); + // TODO return here ? + } + if(_assets == 0) { + return; + } + if(userValues.maxWithdraw >= _assets) { + uint256 sharePrice = calcUserSharePrice( _user, _tranche); + uint256 shares = _assets / sharePrice; + decreaseRedemptionsLimit(_assets, shares); + } else { + transferAmount = _assets - userValues.maxWithdraw; + userValues.maxWithdraw = 0; + userValues.maxRedeem = 0; + + + require(allowedPoolCurrencies[cfgTranche.poolId][address(currency)], "CentrifugeConnector/pool-currency-not-allowed"); + require(currency.balanceOf(_user) >= amount, "CentrifugeConnector/insufficient-balance"); + require(currency.transferFrom(_user, address(escrow), transferAmount), "CentrifugeConnector/currency-transfer-failed"); + } + _increaseInvestOrder(cfgTranche.poolId, cfgTranche.trancheId, address(currency), _assets, _user); + } + + + function transfer(address currencyAddress, bytes32 recipient, uint128 amount) public { uint128 currency = currencyAddressToId[currencyAddress]; require(currency != 0, "CentrifugeConnector/unknown-currency"); @@ -187,7 +225,11 @@ contract CentrifugeConnector is Auth { ); } - function increaseInvestOrder(uint64 poolId, bytes16 trancheId, address currencyAddress, uint128 amount) public { + function increaseInvestOrder(uint64 poolId, bytes16 trancheId, address currencyAddress, uint128 amount ) public { + _increaseInvestOrder(poolId, trancheId, currencyAddress, amount, msq.sender); + } + + function _increaseInvestOrder(uint64 poolId, bytes16 trancheId, address currencyAddress, uint128 amount, address user) internal { RestrictedTokenLike token = RestrictedTokenLike(tranches[poolId][trancheId]); require(address(token) != address(0), "CentrifugeConnector/unknown-tranche-token"); require(token.hasMember(msg.sender), "CentrifugeConnector/not-a-member"); @@ -229,15 +271,19 @@ contract CentrifugeConnector is Auth { } function decreaseRedeemOrder(uint64 poolId, bytes16 trancheId, address currencyAddress, uint128 amount) public { + _decreaseRedeemOrder(poolId, trancheId, currencyAddress, amount, msg.sender); + } + + function _decreaseRedeemOrder(uint64 poolId, bytes16 trancheId, address currencyAddress, uint128 amount, address _user) internal { RestrictedTokenLike token = RestrictedTokenLike(tranches[poolId][trancheId]); require(address(token) != address(0), "CentrifugeConnector/unknown-tranche-token"); - require(token.hasMember(msg.sender), "CentrifugeConnector/not-a-member"); + require(token.hasMember(_user), "CentrifugeConnector/not-a-member"); uint128 currency = currencyAddressToId[currencyAddress]; require(currency != 0, "CentrifugeConnector/unknown-currency"); require(allowedPoolCurrencies[poolId][currencyAddress], "CentrifugeConnector/pool-currency-not-allowed"); - gateway.decreaseRedeemOrder(poolId, trancheId, msg.sender, currency, amount); + gateway.decreaseRedeemOrder(poolId, trancheId, _user, currency, amount); } function collectInvest(uint64 poolId, bytes16 trancheId) public { @@ -297,12 +343,17 @@ contract CentrifugeConnector is Auth { Pool storage pool = pools[_poolId]; require(pool.createdAt > 0, "CentrifugeConnector/invalid-pool"); - address token = tranches[_poolId][_trancheId]; - require(token == address(0), "CentrifugeConnector/tranche-already-added"); + address tranche = tranches[_poolId][_trancheId]; + require(tranche == address(0), "CentrifugeConnector/tranche-already-added"); address asset = address(0x6B175474E89094C44Da98b954EedeAC495271d0F); // TODO FIX : provide tranche currency / assets : default DAI? - token = deployTranche(poolId, trancheId, asset, tokenName, tokenSymbol, decimals); - - TrancheLike(token).updateTokenPrice(_price); + tranche = deployTranche(poolId, trancheId, asset, tokenName, tokenSymbol, decimals); + TrancheLike(tranche).updateTokenPrice(_price); + + // update multi-chain tranche mappings + tranches[_poolId][_trancheId] = tranche; + CFGTranche storage cfgTranche = cfgTranches[poolId][trancheId]; + cfgTranche.poolId = _poolId; + cfgTranche.trancheId = _trancheId; emit TrancheAdded(poolId, trancheId); } @@ -343,7 +394,7 @@ contract CentrifugeConnector is Auth { token.mint(destinationAddress, amount); } - function handleDecreaseInvestOrder(uint64 _poolId, bytes16 _trancheId, address _recipient, uint128 _currency, uint128 _currencyPayout, uint128 _remainingInvestOrder) public onlyGateway { + function handleDecreaseInvestOrder(uint64 _poolId, bytes16 _trancheId, address _user, uint128 _currency, uint128 _currencyPayout, uint128 _remainingInvestOrder) public onlyGateway { require(_currencyPayout != 0, "CentrifugeConnector/zero-payout"); address currencyAddress = currencyIdToAddress[_currency]; address tranche = tranches[_poolId][_trancheId]; @@ -356,26 +407,26 @@ contract CentrifugeConnector is Auth { EscrowLike(escrow).approve(currencyAddress, address(this), _currencyPayout); require( - ERC20Like(currencyAddress).transferFrom(address(escrow), _recipient, _currencyPayout), + ERC20Like(currencyAddress).transferFrom(address(escrow), _user, _currencyPayout), "CentrifugeConnector/currency-transfer-failed" ); - orderbook[_recipient][tranche].openInvest = _remainingInvestOrder; + orderbook[_user][tranche].openInvest = _remainingInvestOrder; } //TODO: currency not really required here - function handleDecreaseRedeemOrder(uint64 _poolId, bytes16 _trancheId, address _recipient, uint128 _currency, uint128 _tokensPayout, uint128 _remainingRedeemOrder) public onlyGateway { + function handleDecreaseRedeemOrder(uint64 _poolId, bytes16 _trancheId, address _user, uint128 _currency, uint128 _tokensPayout, uint128 _remainingRedeemOrder) public onlyGateway { require(_tokensPayout != 0, "CentrifugeConnector/zero-payout"); address tranche = tranches[poolId][trancheId]; require(tranche != address(0), "CentrifugeConnector/tranche-does-not-exist"); - require(RestrictedTokenLike(tranche).hasMember(_recipient), "CentrifugeConnector/not-a-member"); + require(RestrictedTokenLike(tranche).hasMember(_user), "CentrifugeConnector/not-a-member"); // TODO: escrow should give max approval on deployment EscrowLike(escrow).approve(tranche, address(this), _tokensPayout); require( - ERC20Like(tranche).transferFrom(address(escrow), _recipient, _tokensPayout), + ERC20Like(tranche).transferFrom(address(escrow), _user, _tokensPayout), "CentrifugeConnector/trancheTokens-transfer-failed" ); - orderbook[_recipient][tranche].openRedeem = _remainingRedeemOrder; + orderbook[_user][tranche].openRedeem = _remainingRedeemOrder; } function handleCollectInvest(uint64 _poolId, bytes16 _trancheId, address _recepient, uint128 _currency, uint128 _currencyInvested, uint128 _tokensPayout, uint128 _remainingInvestOrder) public onlyGateway { @@ -400,12 +451,8 @@ contract CentrifugeConnector is Auth { values.openRedeem = _remainingRedeemOrder; values.maxWithdraw = values.maxWithdraw + _currencyPayout; values.maxRedeem = values.maxRedeem + _tokensRedeemed; - - - } - // ------ internal helper functions //TODO: rounding @@ -423,10 +470,21 @@ contract CentrifugeConnector is Auth { } } - // function decreaseRedemptionsLimit(uint256 _assets, uint256 _shares) public auth {} + function decreaseRedemptionsLimit(uint256 _assets, uint256 _shares) public auth { + UserTrancheValues values = orderbook[_user][_tranche]; + if (values.maxWithdrawel < _assets) { + values.maxDeposit = 0; + } else { + values.maxWithdrawel = values.maxWithdrawel - _assets; + } + if (values.maxRedemption < _shares) { + values.maxRedemption = 0; + } else { + values.maxRedemption = values.maxRedemption - _shares; + } + } - // TODO: add decimals to constructor of restricted token // TODO: ward setup on tranche contract function deployTranche( uint64 _poolId, diff --git a/src/Tranche4626.sol b/src/Tranche4626.sol index c9906de7..dab3ff40 100644 --- a/src/Tranche4626.sol +++ b/src/Tranche4626.sol @@ -20,14 +20,6 @@ pragma solidity ^0.8.18; // yearn: https://github.com/yearn/yearn-vaults-v3/blob/master/contracts/VaultV3.vy - -// create deposit flow in connectors -// create mint flow -// create redeem flow -// create withdraw flow -// messages collectRedeem & collectInvest - - import "./token/restricted.sol"; interface ConnectorLike { @@ -36,7 +28,10 @@ interface ConnectorLike { function maxDeposit(address _user, address _tranche) external returns (uint256); function maxMint(address _user, address _tranche) external returns (uint256); function maxWithdraw(address _user, address _tranche) external returns (uint256); - function maxRedeem(address _user, address _tranche) external returns (uint256) + function maxRedeem(address _user, address _tranche) external returns (uint256); + function requestRedeem(uint256 _shares, address _receiver) external; + function requestDeposit(uint256 _assets, address _receiver) external; + } /// @title Tranche4626 @@ -95,6 +90,7 @@ contract Tranche4626 is RestrictedToken { /// @dev request asset deposit for a receiver to be included in the next epoch execution. Asset is locked in the escrow on request submission. function requestDeposit(uint256 _assets, address _receiver) auth public { + connector.requestDeposit(address(this), _receiver, _assets); } /// @dev collect shares for deposited funds after pool epoch execution. maxMint is the max amount of shares that can be collected. Required assets must already be locked. @@ -112,7 +108,7 @@ contract Tranche4626 is RestrictedToken { } /// @dev Maximum amount of shares that can be claimed by the receiver after the epoch has been executed on the Centrifuge chain side. - function maxMint(address receiver) external view returns (uint256 maxShares) { + function maxMint(address _receiver) external view returns (uint256 maxShares) { return connector.maxMint(_receiver, address(this)); } @@ -121,6 +117,11 @@ contract Tranche4626 is RestrictedToken { assets = convertToAssets(_shares); } + /// @dev request share redemption for a receiver to be included in the next epoch execution. Shares are locked in the escrow on request submission. + function requestRedeem(uint256 _shares, address _receiver) auth public { + connector.requestRedeem(address(this), _receiver, _shares); + } + /// @dev /// @return function maxWithdraw(address _owner) external view returns (uint256 maxAssets) { From 2b1a4ac75cb36daec383e2a6a5df0a27b5b5497b Mon Sep 17 00:00:00 2001 From: ilinzweilin Date: Tue, 20 Jun 2023 20:32:24 +0100 Subject: [PATCH 08/24] fix function signature --- src/Connector.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Connector.sol b/src/Connector.sol index 4bec653f..f525fb3f 100644 --- a/src/Connector.sol +++ b/src/Connector.sol @@ -148,10 +148,10 @@ contract CentrifugeConnector is Auth { return requiredDeposit; } - function requestRedeem(address _tranche, uint256 _shares, address _user) connectorsActive poolActive(_poolId) auth {} + function requestRedeem(address _tranche, uint256 _shares, address _user) connectorsActive poolActive(_poolId) public auth {} // TODO: fix uint256 - uint128 - function requestDeposit(address _tranche, uint256 _assets, address _user) connectorsActive poolActive(_poolId) auth { + function requestDeposit(address _tranche, uint256 _assets, address _user) connectorsActive poolActive(_poolId) public auth { UserValues userValues = orderbook[_user][_tranche]; CFGTranche cfgTranche = cfgTranches[_tranche]; ERC20Like currency = ERC20Like(TrancheLike(_tranche).asset()); From fe06d4ad8db3b0c24e4af754d6f3e8165d993fff Mon Sep 17 00:00:00 2001 From: ilinzweilin Date: Wed, 21 Jun 2023 17:58:18 +0100 Subject: [PATCH 09/24] add requestRedeem + more refactoring --- src/Connector.sol | 257 ++++++++++++++++++++++++++++------------------ 1 file changed, 159 insertions(+), 98 deletions(-) diff --git a/src/Connector.sol b/src/Connector.sol index f525fb3f..7325a0ac 100644 --- a/src/Connector.sol +++ b/src/Connector.sol @@ -42,6 +42,8 @@ interface TrancheLike { function updateTokenPrice(uint128 _tokenPrice) external; function asset() external returns (address); function mint(address, uint) external; + function balanceOf(address) external returns (uint); + function transferFrom(address, address, uint) external; } interface EscrowLike { @@ -54,6 +56,11 @@ struct Pool { bool isActive; } +struct Tranche { + address token; + address asset; +} + struct CFGTranche { uint64 poolId; bytes16 trancheId; @@ -104,8 +111,9 @@ contract CentrifugeConnector is Auth { emit Rely(msg.sender); } - modifier poolActive(uint64 poolId) { - require(pools[poolId].isActive, "CentrifugeConnector/pool-deactivated"); + modifier poolActive(address _tranche) { + CFGTranche cfgTranche = cfgTranches[_tranche]; + require(pools[cfgTranche.poolId].isActive, "CentrifugeConnector/pool-deactivated"); _; } @@ -128,59 +136,90 @@ contract CentrifugeConnector is Auth { // --- Outgoing message handling --- // auth functions - function deposit(address _tranche, address _user, uint256 _assets) public poolActive(_poolId) connectorsActive auth returns (uint256) { - require((_assets <= orderbook[_user][_tranche].maxDeposit), "CentrifugeConnector/amount-exceeds-deposit-limits"); + function deposit(address _tranche, address _user, uint256 _currencyAmount) public poolActive(_tranche) connectorsActive auth returns (uint256) { + require((_currencyAmount <= orderbook[_user][_tranche].maxDeposit), "CentrifugeConnector/amount-exceeds-deposit-limits"); uint256 sharePrice = calcUserSharePrice( _user, _tranche); require((sharePrice > 0), "Tranche4626/amount-exceeds-deposit-limits"); - uint256 sharesToTransfer = _assets / sharePrice; - decreaseDepositLimits(_user, _tranche, _assets, sharesToTransfer); // decrease the possible deposit limits - require(ERC20Like(_tranche).transferFrom(address(escrow), _user, sharesToTransfer), "CentrifugeConnector/shares-transfer-failed"); - return sharesToTransfer; + uint256 trancheTokensToTransfer = _currencyAmount / sharePrice; + _decreaseDepositLimits(_user, _tranche, _currencyAmount, trancheTokensToTransfer); // decrease the possible deposit limits + require(ERC20Like(_tranche).transferFrom(address(escrow), _user, trancheTokensToTransfer), "CentrifugeConnector/trancheTokens-transfer-failed"); + return trancheTokensToTransfer; } - function mint(address _tranche, address _user, uint256 _shares) public poolActive(_poolId) connectorsActive auth returns (uint256) { - require((_shares <= orderbook[_user][_tranche].maxMint), "CentrifugeConnector/amount-exceeds-mint-limits"); + function mint(address _tranche, address _user, uint256 _trancheTokensAmount) public poolActive(_tranche) connectorsActive auth returns (uint256) { + require((_trancheTokensAmount <= orderbook[_user][_tranche].maxMint), "CentrifugeConnector/amount-exceeds-mint-limits"); uint256 sharePrice = calcUserSharePrice( _user, _tranche); require((sharePrice > 0), "Tranche4626/amount-exceeds-deposit-limits"); - uint256 requiredDeposit = _shares * sharePrice; - decreaseDepositLimits(_user, _tranche, requiredDeposit, _shares); // decrease the possible deposit limits - require(ERC20Like(_tranche).transferFrom(address(escrow), _user, _shares), "CentrifugeConnector/shares-transfer-failed"); + uint256 requiredDeposit = _trancheTokensAmount * sharePrice; + _decreaseDepositLimits(_user, _tranche, requiredDeposit, _trancheTokensAmount); // decrease the possible deposit limits + require(ERC20Like(_tranche).transferFrom(address(escrow), _user, _trancheTokensAmount), "CentrifugeConnector/shares-transfer-failed"); return requiredDeposit; } - function requestRedeem(address _tranche, uint256 _shares, address _user) connectorsActive poolActive(_poolId) public auth {} + function requestRedeem(address _tranche, uint256 _trancheTokensAmount, address _user) connectorsActive poolActive(_tranche) public auth { + UserTrancheValues userValues = orderbook[_user][_tranche]; + CFGTranche cfgTranche = cfgTranches[_tranche]; + TrancheLike tranche = TrancheLike(_tranche); + + require(_poolCurrencyCheck(tranche.asset(), cfgTranche.poolId), "CentrifugeConnector/currency-not-supported"); + require(_trancheTokenCheck(cfgTranche.poolId, cfgTranche.trancheId, _user), "CentrifugeConnector/tranche-tokens-not-supported"); + + if (userValues.openDeposit > 0) { // cancel outstanding deposit orders + // replace + gateway.decreaseInvestOrder(cfgTranche.poolId, cfgTranche.trancheId, _user, tranche.asset(), userValues.openDeposit); + } + if(_trancheTokensAmount == 0) { // case: user justwants to cancel outstanding orders + return; + } + + if(userValues.maxMint >= _trancheTokensAmount) { // case: user has unclaimed trancheTokens in escrow -> more than redemption request + uint256 sharePrice = calcUserSharePrice( _user, _tranche); + uint256 assets = _trancheTokensAmount * sharePrice; + _decreaseDepositLimits(_user, _tranche, assets, _trancheTokensAmount); + } else { + uint transferAmount = _trancheTokensAmount - userValues.maxMint; + userValues.maxDeposit = 0; + userValues.maxMint = 0; + + require(tranche.balanceOf(_user) >= _trancheTokensAmount, "CentrifugeConnector/insufficient-tranche-token-balance"); + require(tranche.transferFrom(_user, address(escrow), transferAmount), "CentrifugeConnector/tranche-token-transfer-failed"); + } + + gateway.increaseRedeemOrder(cfgTranche.poolId, cfgTranche.trancheId, _user, tranche.asset(), _trancheTokensAmount); + } + // TODO: fix uint256 - uint128 - function requestDeposit(address _tranche, uint256 _assets, address _user) connectorsActive poolActive(_poolId) public auth { - UserValues userValues = orderbook[_user][_tranche]; + function requestDeposit(address _tranche, uint _currencyAmount, address _user) connectorsActive poolActive(_tranche) public auth { + UserTrancheValues userValues = orderbook[_user][_tranche]; CFGTranche cfgTranche = cfgTranches[_tranche]; + TrancheLike tranche = TrancheLike(_tranche); ERC20Like currency = ERC20Like(TrancheLike(_tranche).asset()); + + require(_poolCurrencyCheck(tranche.asset(), cfgTranche.poolId), "CentrifugeConnector/currency-not-supported"); + require(_trancheTokenCheck(cfgTranche.poolId, cfgTranche.trancheId, _user), "CentrifugeConnector/tranche-tokens-not-supported"); + if (userValues.openRedeem > 0) { // cancel outstanding redeem orders - _decreaseRedeemOrder(cfgTranche.poolId, cfgTranche.trancheId, userValues.openRedeem, _user); - // TODO return here ? + gateway.decreaseRedeemOrder(cfgTranche.poolId, cfgTranche.trancheId, _user, tranche.asset(), userValues.openRedeem); } - if(_assets == 0) { - return; + if(_currencyAmount == 0) { // case: user only wants to cancel outstanding redemptions + return; } - if(userValues.maxWithdraw >= _assets) { + if(userValues.maxWithdraw >= _currencyAmount) { // case: user has some claimable fund in escrow -> funds > Deposit request uint256 sharePrice = calcUserSharePrice( _user, _tranche); - uint256 shares = _assets / sharePrice; - decreaseRedemptionsLimit(_assets, shares); + uint256 trancheTokens = _currencyAmount / sharePrice; + _decreaseRedemptionLimits(_currencyAmount, trancheTokens); } else { - transferAmount = _assets - userValues.maxWithdraw; + uint transferAmount = _currencyAmount - userValues.maxWithdraw; userValues.maxWithdraw = 0; userValues.maxRedeem = 0; - - require(allowedPoolCurrencies[cfgTranche.poolId][address(currency)], "CentrifugeConnector/pool-currency-not-allowed"); - require(currency.balanceOf(_user) >= amount, "CentrifugeConnector/insufficient-balance"); + require(currency.balanceOf(_user) >= transferAmount, "CentrifugeConnector/insufficient-balance"); require(currency.transferFrom(_user, address(escrow), transferAmount), "CentrifugeConnector/currency-transfer-failed"); } - _increaseInvestOrder(cfgTranche.poolId, cfgTranche.trancheId, address(currency), _assets, _user); + gateway.increaseInvestOrder(cfgTranche.poolId, cfgTranche.trancheId, _user, tranche.asset(), _currencyAmount); } - - - + function transfer(address currencyAddress, bytes32 recipient, uint128 amount) public { uint128 currency = currencyAddressToId[currencyAddress]; require(currency != 0, "CentrifugeConnector/unknown-currency"); @@ -225,81 +264,89 @@ contract CentrifugeConnector is Auth { ); } - function increaseInvestOrder(uint64 poolId, bytes16 trancheId, address currencyAddress, uint128 amount ) public { - _increaseInvestOrder(poolId, trancheId, currencyAddress, amount, msq.sender); - } + // function increaseInvestOrder(uint64 _poolId, bytes16 _trancheId, address _currencyAddress, uint128 _amount ) public { + // _increaseInvestOrder(_poolId, _trancheId, _currencyAddress, _amount, msg.sender); + // } - function _increaseInvestOrder(uint64 poolId, bytes16 trancheId, address currencyAddress, uint128 amount, address user) internal { - RestrictedTokenLike token = RestrictedTokenLike(tranches[poolId][trancheId]); - require(address(token) != address(0), "CentrifugeConnector/unknown-tranche-token"); - require(token.hasMember(msg.sender), "CentrifugeConnector/not-a-member"); + // function _increaseInvestOrder(uint64 _poolId, bytes16 _trancheId, address _currencyAddress, uint128 _amount, address _user) internal { + // RestrictedTokenLike token = RestrictedTokenLike(tranches[_poolId][_trancheId]); + // require(address(token) != address(0), "CentrifugeConnector/unknown-tranche-token"); + // require(token.hasMember(_user), "CentrifugeConnector/not-a-member"); - uint128 currency = currencyAddressToId[currencyAddress]; - require(currency != 0, "CentrifugeConnector/unknown-currency"); - require(allowedPoolCurrencies[poolId][currencyAddress], "CentrifugeConnector/pool-currency-not-allowed"); + // uint128 currency = currencyAddressToId[_currencyAddress]; + // require(currency != 0, "CentrifugeConnector/unknown-currency"); + // require(allowedPoolCurrencies[_poolId][_currencyAddress], "CentrifugeConnector/pool-currency-not-allowed"); - require( - ERC20Like(currencyAddress).transferFrom(msg.sender, address(escrow), amount), - "Centrifuge/Connector/currency-transfer-failed" - ); + // require( + // ERC20Like(_currencyAddress).transferFrom(_user, address(escrow), _amount), + // "Centrifuge/Connector/currency-transfer-failed" + // ); - gateway.increaseInvestOrder(poolId, trancheId, msg.sender, currency, amount); - } + // gateway.increaseInvestOrder(_poolId, _trancheId, _user, currency, _amount); + // } - function decreaseInvestOrder(uint64 poolId, bytes16 trancheId, address currencyAddress, uint128 amount) public { - RestrictedTokenLike token = RestrictedTokenLike(tranches[poolId][trancheId].token); - require(address(token) != address(0), "CentrifugeConnector/unknown-tranche-token"); - require(token.hasMember(msg.sender), "CentrifugeConnector/not-a-member"); + // function decreaseInvestOrder(uint64 _poolId, bytes16 _trancheId, address _currencyAddress, uint128 _amount) public { + // _decreaseInvestOrder(_poolId, _trancheId, _currencyAddress, _amount, msg.sender); + // } - uint128 currency = currencyAddressToId[currencyAddress]; - require(currency != 0, "CentrifugeConnector/unknown-currency"); - require(allowedPoolCurrencies[poolId][currencyAddress], "CentrifugeConnector/pool-currency-not-allowed"); + // function _decreaseInvestOrder(uint64 poolId, bytes16 _trancheId, address _currencyAddress, uint128 _amount, address _user) public { + // // RestrictedTokenLike token = RestrictedTokenLike(tranches[poolId][_trancheId].token); + // // require(address(token) != address(0), "CentrifugeConnector/unknown-tranche-token"); + // // require(token.hasMember(_user), "CentrifugeConnector/not-a-member"); - gateway.decreaseInvestOrder(poolId, trancheId, msg.sender, currency, amount); - } + // uint128 currency = currencyAddressToId[_currencyAddress]; + // require(currency != 0, "CentrifugeConnector/unknown-currency"); + // require(allowedPoolCurrencies[poolId][_currencyAddress], "CentrifugeConnector/pool-currency-not-allowed"); - function increaseRedeemOrder(uint64 poolId, bytes16 trancheId, address currencyAddress, uint128 amount) public { - RestrictedTokenLike token = RestrictedTokenLike(tranches[poolId][trancheId]); - require(address(token) != address(0), "CentrifugeConnector/unknown-tranche-token"); - require(token.hasMember(msg.sender), "CentrifugeConnector/not-a-member"); + // gateway.decreaseInvestOrder(poolId, _trancheId, _user, currency, _amount); + // } - uint128 currency = currencyAddressToId[currencyAddress]; - require(currency != 0, "CentrifugeConnector/unknown-currency"); - require(allowedPoolCurrencies[poolId][currencyAddress], "CentrifugeConnector/pool-currency-not-allowed"); + // function increaseRedeemOrder(uint64 _poolId, bytes16 _trancheId, address _currencyAddress, uint128 _amount) public { + // _increaseRedeemOrder(_poolId, _trancheId, _currencyAddress, _amount, msg.sender); + // } - gateway.increaseRedeemOrder(poolId, trancheId, msg.sender, currency, amount); - } + // function _increaseRedeemOrder(uint64 _poolId, bytes16 _trancheId, address _currencyAddress, uint128 _amount, address _user) internal { + // RestrictedTokenLike token = RestrictedTokenLike(tranches[_poolId][_trancheId]); + // require(address(token) != address(0), "CentrifugeConnector/unknown-tranche-token"); + // require(token.hasMember(_user), "CentrifugeConnector/not-a-member"); - function decreaseRedeemOrder(uint64 poolId, bytes16 trancheId, address currencyAddress, uint128 amount) public { - _decreaseRedeemOrder(poolId, trancheId, currencyAddress, amount, msg.sender); - } + // uint128 currency = currencyAddressToId[_currencyAddress]; + // require(currency != 0, "CentrifugeConnector/unknown-currency"); + // require(allowedPoolCurrencies[_poolId][_currencyAddress], "CentrifugeConnector/pool-currency-not-allowed"); - function _decreaseRedeemOrder(uint64 poolId, bytes16 trancheId, address currencyAddress, uint128 amount, address _user) internal { - RestrictedTokenLike token = RestrictedTokenLike(tranches[poolId][trancheId]); - require(address(token) != address(0), "CentrifugeConnector/unknown-tranche-token"); - require(token.hasMember(_user), "CentrifugeConnector/not-a-member"); + // gateway.increaseRedeemOrder(_poolId, _trancheId, _user, currency, _amount); + // } - uint128 currency = currencyAddressToId[currencyAddress]; - require(currency != 0, "CentrifugeConnector/unknown-currency"); - require(allowedPoolCurrencies[poolId][currencyAddress], "CentrifugeConnector/pool-currency-not-allowed"); + // function decreaseRedeemOrder(uint64 _poolId, bytes16 _trancheId, address _currencyAddress, uint128 _amount) public { + // _decreaseRedeemOrder(_poolId, _trancheId, _currencyAddress, _amount, msg.sender); + // } - gateway.decreaseRedeemOrder(poolId, trancheId, _user, currency, amount); - } + // function _decreaseRedeemOrder(uint64 _poolId, bytes16 _trancheId, address _currencyAddress, uint128 _amount, address _user) internal { + // RestrictedTokenLike token = RestrictedTokenLike(tranches[_poolId][_trancheId]); + // require(address(token) != address(0), "CentrifugeConnector/unknown-tranche-token"); + // require(token.hasMember(_user), "CentrifugeConnector/not-a-member"); - function collectInvest(uint64 poolId, bytes16 trancheId) public { - RestrictedTokenLike token = RestrictedTokenLike(tranches[poolId][trancheId]); + // uint128 currency = currencyAddressToId[_currencyAddress]; + // require(currency != 0, "CentrifugeConnector/unknown-currency"); + // require(allowedPoolCurrencies[_poolId][_currencyAddress], "CentrifugeConnector/pool-currency-not-allowed"); + + // gateway.decreaseRedeemOrder(_poolId, _trancheId, _user, currency, _amount); + // } + + function collectInvest(uint64 _poolId, bytes16 _trancheId) public { + RestrictedTokenLike token = RestrictedTokenLike(tranches[_poolId][_trancheId]); require(address(token) != address(0), "CentrifugeConnector/unknown-tranche-token"); require(token.hasMember(msg.sender), "CentrifugeConnector/not-a-member"); - gateway.collectInvest(poolId, trancheId, address(msg.sender)); + gateway.collectInvest(_poolId, _trancheId, address(msg.sender)); } - function collectRedeem(uint64 poolId, bytes16 trancheId) public { - RestrictedTokenLike token = RestrictedTokenLike(tranches[poolId][trancheId]); + function collectRedeem(uint64 _poolId, bytes16 _trancheId) public { + RestrictedTokenLike token = RestrictedTokenLike(tranches[_poolId][_trancheId]); require(address(token) != address(0), "CentrifugeConnector/unknown-tranche-token"); require(token.hasMember(msg.sender), "CentrifugeConnector/not-a-member"); - gateway.collectRedeem(poolId, trancheId, address(msg.sender)); + gateway.collectRedeem(_poolId, _trancheId, address(msg.sender)); } // --- Incoming message handling --- @@ -346,16 +393,16 @@ contract CentrifugeConnector is Auth { address tranche = tranches[_poolId][_trancheId]; require(tranche == address(0), "CentrifugeConnector/tranche-already-added"); address asset = address(0x6B175474E89094C44Da98b954EedeAC495271d0F); // TODO FIX : provide tranche currency / assets : default DAI? - tranche = deployTranche(poolId, trancheId, asset, tokenName, tokenSymbol, decimals); + tranche = deployTranche(_poolId, _trancheId, asset, _tokenName, _tokenSymbol, _decimals); TrancheLike(tranche).updateTokenPrice(_price); // update multi-chain tranche mappings tranches[_poolId][_trancheId] = tranche; - CFGTranche storage cfgTranche = cfgTranches[poolId][trancheId]; + CFGTranche storage cfgTranche = cfgTranches[_poolId][_trancheId]; cfgTranche.poolId = _poolId; cfgTranche.trancheId = _trancheId; - emit TrancheAdded(poolId, trancheId); + emit TrancheAdded(_poolId, _trancheId); } function updateTokenPrice(uint64 _poolId, bytes16 _trancheId, uint128 _price) public onlyGateway { @@ -456,31 +503,46 @@ contract CentrifugeConnector is Auth { // ------ internal helper functions //TODO: rounding - function decreaseDepositLimits(address _user, address _tranche, uint256 _assets, uint256 _shares) internal { + + function _poolCurrencyCheck(uint64 _poolId, address _currencyAddress) internal returns (bool) { + uint128 currency = currencyAddressToId[_currencyAddress]; + require(currency != 0, "CentrifugeConnector/unknown-currency"); + require(allowedPoolCurrencies[_poolId][_currencyAddress], "CentrifugeConnector/pool-currency-not-allowed"); + return true; + } + + function _trancheTokenCheck(uint64 _poolId, bytes16 _trancheId, address _user) internal returns (bool) { + RestrictedTokenLike token = RestrictedTokenLike(tranches[_poolId][_trancheId]); + require(address(token) != address(0), "CentrifugeConnector/unknown-tranche-token"); + require(token.hasMember(_user), "CentrifugeConnector/not-a-member"); + return true; + } + + function _decreaseDepositLimits(address _user, address _tranche, uint256 _currency, uint256 _trancheTokens) internal { UserTrancheValues values = orderbook[_user][_tranche]; - if (values.maxDeposit < _assets) { + if (values.maxDeposit < _currency) { values.maxDeposit = 0; } else { - values.maxDeposit = values.maxDeposit - _assets; + values.maxDeposit = values.maxDeposit - _currency; } - if (values.maxMint < _shares) { + if (values.maxMint < _trancheTokens) { values.maxMint = 0; } else { - values.maxMint = values.maxMint - _shares; + values.maxMint = values.maxMint - _trancheTokens; } } - function decreaseRedemptionsLimit(uint256 _assets, uint256 _shares) public auth { + function _decreaseRedemptionLimits(address _user, address _tranche, uint256 _currency, uint256 _trancheTokens) internal { UserTrancheValues values = orderbook[_user][_tranche]; - if (values.maxWithdrawel < _assets) { + if (values.maxWithdrawel < _currency) { values.maxDeposit = 0; } else { - values.maxWithdrawel = values.maxWithdrawel - _assets; + values.maxWithdrawel = values.maxWithdrawel - _currency; } - if (values.maxRedemption < _shares) { + if (values.maxRedemption < _trancheTokens) { values.maxRedemption = 0; } else { - values.maxRedemption = values.maxRedemption - _shares; + values.maxRedemption = values.maxRedemption - _trancheTokens; } } @@ -528,6 +590,5 @@ contract CentrifugeConnector is Auth { function maxRedeem(address _user, address _tranche) public view returns (uint256) { return orderbook[_user][_tranche].maxRedeem; - } - + } } From a6f10dd30d2351706f99a0abf2598fc523e930eb Mon Sep 17 00:00:00 2001 From: ilinzweilin Date: Wed, 21 Jun 2023 20:01:14 +0100 Subject: [PATCH 10/24] Connector - add processRedeem & processWitdraw flows --- src/Connector.sol | 78 +++++++++++++++++++++++++++++---------------- src/Tranche4626.sol | 65 +++++++++++++++++++++++-------------- 2 files changed, 92 insertions(+), 51 deletions(-) diff --git a/src/Connector.sol b/src/Connector.sol index 7325a0ac..c75eb72b 100644 --- a/src/Connector.sol +++ b/src/Connector.sol @@ -136,24 +136,45 @@ contract CentrifugeConnector is Auth { // --- Outgoing message handling --- // auth functions - function deposit(address _tranche, address _user, uint256 _currencyAmount) public poolActive(_tranche) connectorsActive auth returns (uint256) { + function processDeposit(address _tranche, address _user, uint256 _currencyAmount) public poolActive(_tranche) connectorsActive auth returns (uint256) { require((_currencyAmount <= orderbook[_user][_tranche].maxDeposit), "CentrifugeConnector/amount-exceeds-deposit-limits"); - uint256 sharePrice = calcUserSharePrice( _user, _tranche); - require((sharePrice > 0), "Tranche4626/amount-exceeds-deposit-limits"); - uint256 trancheTokensToTransfer = _currencyAmount / sharePrice; - _decreaseDepositLimits(_user, _tranche, _currencyAmount, trancheTokensToTransfer); // decrease the possible deposit limits - require(ERC20Like(_tranche).transferFrom(address(escrow), _user, trancheTokensToTransfer), "CentrifugeConnector/trancheTokens-transfer-failed"); - return trancheTokensToTransfer; + uint256 userTrancheTokenPrice = calcCustomTrancheTokenPrice( _user, _tranche); + require((userTrancheTokenPrice > 0), "Tranche4626/amount-exceeds-deposit-limits"); + uint256 trancheTokensPayout = _currencyAmount / userTrancheTokenPrice; + _decreaseDepositLimits(_user, _tranche, _currencyAmount, trancheTokensPayout); // decrease the possible deposit limits + require(ERC20Like(_tranche).transferFrom(address(escrow), _user, trancheTokensPayout), "CentrifugeConnector/trancheTokens-transfer-failed"); + return trancheTokensPayout; } - function mint(address _tranche, address _user, uint256 _trancheTokensAmount) public poolActive(_tranche) connectorsActive auth returns (uint256) { + function processMint(address _tranche, address _user, uint256 _trancheTokensAmount) public poolActive(_tranche) connectorsActive auth returns (uint256) { require((_trancheTokensAmount <= orderbook[_user][_tranche].maxMint), "CentrifugeConnector/amount-exceeds-mint-limits"); - uint256 sharePrice = calcUserSharePrice( _user, _tranche); - require((sharePrice > 0), "Tranche4626/amount-exceeds-deposit-limits"); - uint256 requiredDeposit = _trancheTokensAmount * sharePrice; - _decreaseDepositLimits(_user, _tranche, requiredDeposit, _trancheTokensAmount); // decrease the possible deposit limits + uint256 userTrancheTokenPrice = calcCustomTrancheTokenPrice( _user, _tranche); + require((userTrancheTokenPrice > 0), "Tranche4626/amount-exceeds-mint-limits"); + uint256 currencyDeposited = _trancheTokensAmount * userTrancheTokenPrice; + _decreaseDepositLimits(_user, _tranche, currencyDeposited, _trancheTokensAmount); // decrease the possible deposit limits require(ERC20Like(_tranche).transferFrom(address(escrow), _user, _trancheTokensAmount), "CentrifugeConnector/shares-transfer-failed"); - return requiredDeposit; + return currencyDeposited; + } + + function processWithdraw(address _tranche, uint256 _currencyAmount, address _receiver, address _user) public poolActive(_tranche) connectorsActive auth returns (uint256) { + require((_currencyAmount <= orderbook[_user][_tranche].maxWithdaw), "CentrifugeConnector/amount-exceeds-withdraw-limits"); + TrancheLike tranche = TrancheLike(_tranche); + uint256 userTrancheTokenPrice = calcCustomTrancheTokenPrice(_user, _tranche); + require((userTrancheTokenPrice > 0), "Tranche4626/amount-exceeds-withdraw-limits"); + uint256 redeemedTrancheTokens = _currencyAmount / userTrancheTokenPrice; + _decreaseRedemptionLimits(_user, _tranche, _currencyAmount, redeemedTrancheTokens); + require(ERC20Like(tranche.asset()).transferFrom(address(escrow), _receiver, _currencyAmount), "CentrifugeConnector/trancheTokens-transfer-failed"); + return redeemedTrancheTokens; + } + + function processRedeem(address _tranche, uint256 _trancheTokensAmount, address _receiver, address _user) public poolActive(_tranche) connectorsActive auth returns (uint256) { + require((_trancheTokensAmount <= orderbook[_user][_tranche].maxRedeem), "CentrifugeConnector/amount-exceeds-redeem-limits"); + uint256 userTrancheTokenPrice = calcCustomTrancheTokenPrice( _user, _tranche); + require((userTrancheTokenPrice > 0), "Tranche4626/amount-exceeds-redemption-limits"); + uint256 currencyPayout = _trancheTokensAmount * userTrancheTokenPrice; + _decreaseRedemptionLimits(_user, _tranche, currencyPayout, _trancheTokensAmount); // decrease the possible deposit limits + require(ERC20Like(_tranche).transferFrom(address(escrow), _receiver, currencyPayout), "CentrifugeConnector/shares-transfer-failed"); + return currencyPayout; } function requestRedeem(address _tranche, uint256 _trancheTokensAmount, address _user) connectorsActive poolActive(_tranche) public auth { @@ -173,8 +194,8 @@ contract CentrifugeConnector is Auth { } if(userValues.maxMint >= _trancheTokensAmount) { // case: user has unclaimed trancheTokens in escrow -> more than redemption request - uint256 sharePrice = calcUserSharePrice( _user, _tranche); - uint256 assets = _trancheTokensAmount * sharePrice; + uint256 userTrancheTokenPrice = calcCustomTrancheTokenPrice( _user, _tranche); + uint256 assets = _trancheTokensAmount * userTrancheTokenPrice; _decreaseDepositLimits(_user, _tranche, assets, _trancheTokensAmount); } else { uint transferAmount = _trancheTokensAmount - userValues.maxMint; @@ -206,9 +227,9 @@ contract CentrifugeConnector is Auth { return; } if(userValues.maxWithdraw >= _currencyAmount) { // case: user has some claimable fund in escrow -> funds > Deposit request - uint256 sharePrice = calcUserSharePrice( _user, _tranche); - uint256 trancheTokens = _currencyAmount / sharePrice; - _decreaseRedemptionLimits(_currencyAmount, trancheTokens); + uint256 userTrancheTokenPrice = calcCustomTrancheTokenPrice( _user, _tranche); + uint256 trancheTokens = _currencyAmount / userTrancheTokenPrice; + _decreaseRedemptionLimits(_user, _tranche, _currencyAmount, trancheTokens); } else { uint transferAmount = _currencyAmount - userValues.maxWithdraw; userValues.maxWithdraw = 0; @@ -217,6 +238,7 @@ contract CentrifugeConnector is Auth { require(currency.balanceOf(_user) >= transferAmount, "CentrifugeConnector/insufficient-balance"); require(currency.transferFrom(_user, address(escrow), transferAmount), "CentrifugeConnector/currency-transfer-failed"); } + gateway.increaseInvestOrder(cfgTranche.poolId, cfgTranche.trancheId, _user, tranche.asset(), _currencyAmount); } @@ -478,10 +500,10 @@ contract CentrifugeConnector is Auth { function handleCollectInvest(uint64 _poolId, bytes16 _trancheId, address _recepient, uint128 _currency, uint128 _currencyInvested, uint128 _tokensPayout, uint128 _remainingInvestOrder) public onlyGateway { require(_currencyInvested != 0, "CentrifugeConnector/zero-invest"); - address tranche = tranches[poolId][trancheId]; + address tranche = tranches[_poolId][_trancheId]; require(tranche != address(0), "CentrifugeConnector/tranche-does-not-exist"); - UserTrancheValues values = orderbook[_recepient][_tranche]; + UserTrancheValues values = orderbook[_recepient][tranche]; values.openInvest = _remainingInvestOrder; values.maxDeposit = values.maxDeposit + _currencyInvested; values.maxMint = values.maxMint + _tokensPayout; @@ -489,15 +511,17 @@ contract CentrifugeConnector is Auth { TrancheLike(tranche).mint(address(escrow), _tokensPayout); // mint to escrow. Recepeint can claim by calling withdraw / redeem } - function handleCollectRedeem(uint64 _poolId, bytes16 _trancheId, address _recepient, uint128 _currency, uint128 _currencyPayout, uint128 _tokensRedeemed, uint128 _remainingRedeemOrder) public onlyGateway { - require(_tokensRedeemed != 0, "CentrifugeConnector/zero-redeem"); - address tranche = tranches[poolId][trancheId]; + function handleCollectRedeem(uint64 _poolId, bytes16 _trancheId, address _recepient, uint128 _currency, uint128 _currencyPayout, uint128 _trancheTokensRedeemed, uint128 _remainingRedeemOrder) public onlyGateway { + require(_trancheTokensRedeemed != 0, "CentrifugeConnector/zero-redeem"); + address tranche = tranches[_poolId][_trancheId]; require(tranche != address(0), "CentrifugeConnector/tranche-does-not-exist"); - UserTrancheValues values = orderbook[_recepient][_tranche]; + UserTrancheValues values = orderbook[_recepient][tranche]; values.openRedeem = _remainingRedeemOrder; values.maxWithdraw = values.maxWithdraw + _currencyPayout; - values.maxRedeem = values.maxRedeem + _tokensRedeemed; + values.maxRedeem = values.maxRedeem + _trancheTokensRedeemed; + + TrancheLike(tranche).burn(address(escrow), _trancheTokensRedeemed); // burned redeemed tokens from escrow } // ------ internal helper functions @@ -568,12 +592,12 @@ contract CentrifugeConnector is Auth { // ------ EIP 4626 view functions /// @dev calculates the avg share price for the deposited assets of a specific user - function calcUserSharePrice(address _user, address _tranche) public view returns (uint256 sharePrice) { + function calcCustomTrancheTokenPrice(address _user, address _tranche) public view returns (uint256 userTrancheTokenPrice) { UserTrancheValues values = orderbook[_user][_tranche]; if(values.maxMint == 0) { return 0; } - sharePrice = values.maxDeposit / values.maxMint; + userTrancheTokenPrice = values.maxDeposit / values.maxMint; } function maxDeposit(address _user, address _tranche) public view returns (uint256) { diff --git a/src/Tranche4626.sol b/src/Tranche4626.sol index dab3ff40..1f443029 100644 --- a/src/Tranche4626.sol +++ b/src/Tranche4626.sol @@ -23,8 +23,9 @@ pragma solidity ^0.8.18; import "./token/restricted.sol"; interface ConnectorLike { - function deposit(address _tranche, address _receiver, uint256 _assets) external returns (uint256); - function mint(address _tranche, address _receiver, uint256 _shares) external returns (uint256); + function processDeposit(address _tranche, address _receiver, uint256 _assets) external returns (uint256); + function processMint(address _tranche, address _receiver, uint256 _shares) external returns (uint256); + function processWithdraw(address _tranche, uint256 _assets, address _receiver, address _owner) external returns (uint256); function maxDeposit(address _user, address _tranche) external returns (uint256); function maxMint(address _user, address _tranche) external returns (uint256); function maxWithdraw(address _user, address _tranche) external returns (uint256); @@ -96,14 +97,14 @@ contract Tranche4626 is RestrictedToken { /// @dev collect shares for deposited funds after pool epoch execution. maxMint is the max amount of shares that can be collected. Required assets must already be locked. /// maxDeposit is the amount of funds that was successfully invested into the pool on Centrifuge chain function deposit(uint256 _assets, address _receiver) auth public returns (uint256 shares) { - uint transferredShares = connector.deposit(address(this), _receiver, _assets); + uint transferredShares = connector.processDeposit(address(this), _receiver, _assets); Deposit(address(this), _receiver, _assets, transferredShares); } /// @dev collect shares for deposited funds after pool epoch execution. maxMint is the max amount of shares that can be collected. Required assets must already be locked. /// maxDeposit is the amount of funds that was successfully invested into the pool on Centrifuge chain - function mint(uint256 _shares, address _receiver) auth public returns (uint256 assets) { - uint lockedAssets = connector.mint(address(this), _receiver, _shares); + function mint(uint256 _shares, address _receiver) auth public returns (uint256 assets) { + uint lockedAssets = connector.processMint(address(this), _receiver, _shares); Deposit(address(this), _receiver, lockedAssets, _shares); } @@ -122,30 +123,46 @@ contract Tranche4626 is RestrictedToken { connector.requestRedeem(address(this), _receiver, _shares); } - /// @dev + /// @dev /// @return - function maxWithdraw(address _owner) external view returns (uint256 maxAssets) { - return connector.maxWithdraw(_owner, address(this)); + function maxWithdraw(address _receiver) public view returns (uint256 maxAssets) { + return connector.maxWithdraw(_receiver, address(this)); } - /// @dev - /// @return - function previewWithdraw(uint256 assets) external view returns (uint256 shares); - /// @dev - /// @return - function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares); - /// @dev - /// @return - function maxRedeem(address owner) external view returns (uint256 maxShares) { - return connector.maxRedeem(_owner, address(this)); + + /// @return The amount of shares a user would need to redeem in order to receive the given amount of assets -> convertToAssets + function previewWithdraw(uint256 _assets) public view returns (uint256 shares) { + shares = convertToShares(_assets); + } + + /// @dev Withdraw assets after successful epoch execution. Receiver will receive an exact amount of _assets for a certain amount of shares that has been redeemed from Owner during epoch execution. + /// @return shares that have been redeemed for the excat _assets amount + function withdraw(uint256 _assets, address _receiver, address _owner) auth public returns (uint256 shares) { + uint sharesRedeemed = connector.processWithdraw(address(this), _assets, _receiver, _owner); + Withdraw(address(this), _receiver, _owner, _assets, sharesRedeemed); + return sharesRedeemed; + } + + /// @dev Max amount of shares that can be redeemed by the owner after redemption was requested + function maxRedeem(address owner) public view returns (uint256 maxShares) { + return connector.maxRedeem(owner, address(this)); + } + + + /// @return The amount of assets that any user could redeem for an given amount of shares -> convertToAssets + function previewRedeem(uint256 _shares) public view returns (uint256 assets) { + assets = convertToAssets(_shares); + } + + /// @dev Redeem shares after successful epoch execution. Receiver will receive assets for the exact amount of redeemed shares from Owner after epoch execution. + /// @return assets currency payout for the exact amount of redeemed _shares + function redeem(uint256 _shares, address _receiver, address _owner) auth public returns (uint256 assets) { + uint currencyPayout = connector.processWithdraw(address(this), _shares, _receiver, _owner); + Withdraw(address(this), _receiver, _owner, currencyPayout, _shares); + return currencyPayout; } - /// @dev - /// @return - function previewRedeem(uint256 shares) external view returns (uint256 assets); - /// @dev - /// @return - function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets); + // auth functions function updateTokenPrice(uint128 _tokenPrice) public auth { latestPrice = _tokenPrice; lastPriceUpdate = block.timestamp; From e644a160a49f9277ad4c566e24580641421ddc56 Mon Sep 17 00:00:00 2001 From: ilinzweilin Date: Thu, 22 Jun 2023 14:40:09 +0100 Subject: [PATCH 11/24] refactor and build fixes --- src/Connector.sol | 177 ++++++++++++++++++++------------------- src/Tranche4626.sol | 13 ++- src/routers/Gateway.sol | 18 +--- src/token/restricted.sol | 2 +- 4 files changed, 99 insertions(+), 111 deletions(-) diff --git a/src/Connector.sol b/src/Connector.sol index c75eb72b..015f9151 100644 --- a/src/Connector.sol +++ b/src/Connector.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.18; pragma abicoder v2; import {TrancheTokenFactoryLike, MemberlistFactoryLike} from "./token/factory.sol"; -import {RestrictedTokenLike, ERC20Like} from "./token/restricted.sol"; +import {ERC20Like} from "./token/restricted.sol"; import {MemberlistLike} from "./token/memberlist.sol"; import "./auth/auth.sol"; @@ -39,11 +39,20 @@ interface GatewayLike { // is RestrictedToken interface TrancheLike { - function updateTokenPrice(uint128 _tokenPrice) external; - function asset() external returns (address); + // restricted functions + function memberlist() external returns (address); + function hasMember(address) external returns (bool); + function file(bytes32 what, address data) external; + //erc20 functions function mint(address, uint) external; + function burn(address, uint) external; function balanceOf(address) external returns (uint); - function transferFrom(address, address, uint) external; + function transferFrom(address, address, uint) external returns (bool); + // 4626 functions + function updateTokenPrice(uint128 _tokenPrice) external; + function asset() external returns (address); + + } interface EscrowLike { @@ -61,25 +70,25 @@ struct Tranche { address asset; } -struct CFGTranche { +struct CentrifugeTranche { uint64 poolId; bytes16 trancheId; } struct UserTrancheValues { - uint256 maxDeposit; - uint256 maxMint; - uint256 maxWithdraw; - uint256 maxRedeem; - uint256 openRedeem; - uint256 openInvest; + uint maxDeposit; + uint maxMint; + uint maxWithdraw; + uint maxRedeem; + uint openRedeem; + uint openInvest; } contract CentrifugeConnector is Auth { mapping(uint64 => Pool) public pools; mapping(uint64 => mapping(bytes16 => address)) public tranches; - mapping(address => CFGTranche) public cfgTranches; // maps evm tranches to CFG tranches + mapping(address => CentrifugeTranche) public centrifugeTranches; mapping(address => mapping(address => UserTrancheValues)) public orderbook; // contains outstanding orders and limits for each user and tranche mapping(uint128 => address) public currencyIdToAddress; @@ -112,8 +121,8 @@ contract CentrifugeConnector is Auth { } modifier poolActive(address _tranche) { - CFGTranche cfgTranche = cfgTranches[_tranche]; - require(pools[cfgTranche.poolId].isActive, "CentrifugeConnector/pool-deactivated"); + CentrifugeTranche memory cTranche = centrifugeTranches[_tranche]; + require(pools[cTranche.poolId].isActive, "CentrifugeConnector/pool-deactivated"); _; } @@ -142,7 +151,7 @@ contract CentrifugeConnector is Auth { require((userTrancheTokenPrice > 0), "Tranche4626/amount-exceeds-deposit-limits"); uint256 trancheTokensPayout = _currencyAmount / userTrancheTokenPrice; _decreaseDepositLimits(_user, _tranche, _currencyAmount, trancheTokensPayout); // decrease the possible deposit limits - require(ERC20Like(_tranche).transferFrom(address(escrow), _user, trancheTokensPayout), "CentrifugeConnector/trancheTokens-transfer-failed"); + require(TrancheLike(_tranche).transferFrom(address(escrow), _user, trancheTokensPayout), "CentrifugeConnector/trancheTokens-transfer-failed"); return trancheTokensPayout; } @@ -152,12 +161,12 @@ contract CentrifugeConnector is Auth { require((userTrancheTokenPrice > 0), "Tranche4626/amount-exceeds-mint-limits"); uint256 currencyDeposited = _trancheTokensAmount * userTrancheTokenPrice; _decreaseDepositLimits(_user, _tranche, currencyDeposited, _trancheTokensAmount); // decrease the possible deposit limits - require(ERC20Like(_tranche).transferFrom(address(escrow), _user, _trancheTokensAmount), "CentrifugeConnector/shares-transfer-failed"); + require(TrancheLike(_tranche).transferFrom(address(escrow), _user, _trancheTokensAmount), "CentrifugeConnector/shares-transfer-failed"); return currencyDeposited; } function processWithdraw(address _tranche, uint256 _currencyAmount, address _receiver, address _user) public poolActive(_tranche) connectorsActive auth returns (uint256) { - require((_currencyAmount <= orderbook[_user][_tranche].maxWithdaw), "CentrifugeConnector/amount-exceeds-withdraw-limits"); + require((_currencyAmount <= orderbook[_user][_tranche].maxWithdraw), "CentrifugeConnector/amount-exceeds-withdraw-limits"); TrancheLike tranche = TrancheLike(_tranche); uint256 userTrancheTokenPrice = calcCustomTrancheTokenPrice(_user, _tranche); require((userTrancheTokenPrice > 0), "Tranche4626/amount-exceeds-withdraw-limits"); @@ -173,21 +182,21 @@ contract CentrifugeConnector is Auth { require((userTrancheTokenPrice > 0), "Tranche4626/amount-exceeds-redemption-limits"); uint256 currencyPayout = _trancheTokensAmount * userTrancheTokenPrice; _decreaseRedemptionLimits(_user, _tranche, currencyPayout, _trancheTokensAmount); // decrease the possible deposit limits - require(ERC20Like(_tranche).transferFrom(address(escrow), _receiver, currencyPayout), "CentrifugeConnector/shares-transfer-failed"); + require(TrancheLike(_tranche).transferFrom(address(escrow), _receiver, currencyPayout), "CentrifugeConnector/shares-transfer-failed"); return currencyPayout; } function requestRedeem(address _tranche, uint256 _trancheTokensAmount, address _user) connectorsActive poolActive(_tranche) public auth { - UserTrancheValues userValues = orderbook[_user][_tranche]; - CFGTranche cfgTranche = cfgTranches[_tranche]; + UserTrancheValues memory userValues = orderbook[_user][_tranche]; + CentrifugeTranche memory cTranche = centrifugeTranches[_tranche]; TrancheLike tranche = TrancheLike(_tranche); - require(_poolCurrencyCheck(tranche.asset(), cfgTranche.poolId), "CentrifugeConnector/currency-not-supported"); - require(_trancheTokenCheck(cfgTranche.poolId, cfgTranche.trancheId, _user), "CentrifugeConnector/tranche-tokens-not-supported"); + require(_poolCurrencyCheck(cTranche.poolId, tranche.asset()), "CentrifugeConnector/currency-not-supported"); + require(_trancheTokenCheck(cTranche.poolId, cTranche.trancheId, _user), "CentrifugeConnector/tranche-tokens-not-supported"); - if (userValues.openDeposit > 0) { // cancel outstanding deposit orders + if (userValues.openInvest > 0) { // cancel outstanding deposit orders // replace - gateway.decreaseInvestOrder(cfgTranche.poolId, cfgTranche.trancheId, _user, tranche.asset(), userValues.openDeposit); + gateway.decreaseInvestOrder(cTranche.poolId, cTranche.trancheId, _user, currencyAddressToId[tranche.asset()], uint128(userValues.openInvest)); } if(_trancheTokensAmount == 0) { // case: user justwants to cancel outstanding orders return; @@ -206,22 +215,22 @@ contract CentrifugeConnector is Auth { require(tranche.transferFrom(_user, address(escrow), transferAmount), "CentrifugeConnector/tranche-token-transfer-failed"); } - gateway.increaseRedeemOrder(cfgTranche.poolId, cfgTranche.trancheId, _user, tranche.asset(), _trancheTokensAmount); + gateway.increaseRedeemOrder(cTranche.poolId, cTranche.trancheId, _user, currencyAddressToId[tranche.asset()], uint128(_trancheTokensAmount)); } // TODO: fix uint256 - uint128 function requestDeposit(address _tranche, uint _currencyAmount, address _user) connectorsActive poolActive(_tranche) public auth { - UserTrancheValues userValues = orderbook[_user][_tranche]; - CFGTranche cfgTranche = cfgTranches[_tranche]; + UserTrancheValues memory userValues = orderbook[_user][_tranche]; + CentrifugeTranche memory cTranche = centrifugeTranches[_tranche]; TrancheLike tranche = TrancheLike(_tranche); ERC20Like currency = ERC20Like(TrancheLike(_tranche).asset()); - require(_poolCurrencyCheck(tranche.asset(), cfgTranche.poolId), "CentrifugeConnector/currency-not-supported"); - require(_trancheTokenCheck(cfgTranche.poolId, cfgTranche.trancheId, _user), "CentrifugeConnector/tranche-tokens-not-supported"); + require(_poolCurrencyCheck(cTranche.poolId, tranche.asset()), "CentrifugeConnector/currency-not-supported"); + require(_trancheTokenCheck(cTranche.poolId, cTranche.trancheId, _user), "CentrifugeConnector/tranche-tokens-not-supported"); if (userValues.openRedeem > 0) { // cancel outstanding redeem orders - gateway.decreaseRedeemOrder(cfgTranche.poolId, cfgTranche.trancheId, _user, tranche.asset(), userValues.openRedeem); + gateway.decreaseRedeemOrder(cTranche.poolId, cTranche.trancheId, _user, currencyAddressToId[tranche.asset()], uint128(userValues.openRedeem)); } if(_currencyAmount == 0) { // case: user only wants to cancel outstanding redemptions return; @@ -239,7 +248,7 @@ contract CentrifugeConnector is Auth { require(currency.transferFrom(_user, address(escrow), transferAmount), "CentrifugeConnector/currency-transfer-failed"); } - gateway.increaseInvestOrder(cfgTranche.poolId, cfgTranche.trancheId, _user, tranche.asset(), _currencyAmount); + gateway.increaseInvestOrder(cTranche.poolId, cTranche.trancheId, _user, currencyAddressToId[tranche.asset()], uint128(_currencyAmount)); } function transfer(address currencyAddress, bytes32 recipient, uint128 amount) public { @@ -259,11 +268,11 @@ contract CentrifugeConnector is Auth { bytes32 destinationAddress, uint128 amount ) public { - RestrictedTokenLike token = RestrictedTokenLike(tranches[poolId][trancheId]); - require(address(token) != address(0), "CentrifugeConnector/unknown-token"); + TrancheLike tranche = TrancheLike(tranches[poolId][trancheId]); + require(address(tranche) != address(0), "CentrifugeConnector/unknown-token"); - require(token.balanceOf(msg.sender) >= amount, "CentrifugeConnector/insufficient-balance"); - token.burn(msg.sender, amount); + require(tranche.balanceOf(msg.sender) >= amount, "CentrifugeConnector/insufficient-balance"); + tranche.burn(msg.sender, amount); gateway.transferTrancheTokensToCentrifuge(poolId, trancheId, msg.sender, destinationAddress, amount); } @@ -275,11 +284,11 @@ contract CentrifugeConnector is Auth { address destinationAddress, uint128 amount ) public { - RestrictedTokenLike token = RestrictedTokenLike(tranches[poolId][trancheId]); - require(address(token) != address(0), "CentrifugeConnector/unknown-token"); + TrancheLike tranche = TrancheLike(tranches[poolId][trancheId]); + require(address(tranche) != address(0), "CentrifugeConnector/unknown-token"); - require(token.balanceOf(msg.sender) >= amount, "CentrifugeConnector/insufficient-balance"); - token.burn(msg.sender, amount); + require(tranche.balanceOf(msg.sender) >= amount, "CentrifugeConnector/insufficient-balance"); + tranche.burn(msg.sender, amount); gateway.transferTrancheTokensToEVM( poolId, trancheId, msg.sender, destinationChainId, destinationAddress, amount @@ -356,17 +365,17 @@ contract CentrifugeConnector is Auth { // } function collectInvest(uint64 _poolId, bytes16 _trancheId) public { - RestrictedTokenLike token = RestrictedTokenLike(tranches[_poolId][_trancheId]); - require(address(token) != address(0), "CentrifugeConnector/unknown-tranche-token"); - require(token.hasMember(msg.sender), "CentrifugeConnector/not-a-member"); + TrancheLike tranche = TrancheLike(tranches[_poolId][_trancheId]); + require(address(tranche) != address(0), "CentrifugeConnector/unknown-tranche-token"); + require(tranche.hasMember(msg.sender), "CentrifugeConnector/not-a-member"); gateway.collectInvest(_poolId, _trancheId, address(msg.sender)); } function collectRedeem(uint64 _poolId, bytes16 _trancheId) public { - RestrictedTokenLike token = RestrictedTokenLike(tranches[_poolId][_trancheId]); - require(address(token) != address(0), "CentrifugeConnector/unknown-tranche-token"); - require(token.hasMember(msg.sender), "CentrifugeConnector/not-a-member"); + TrancheLike tranche = TrancheLike(tranches[_poolId][_trancheId]); + require(address(tranche) != address(0), "CentrifugeConnector/unknown-tranche-token"); + require(tranche.hasMember(msg.sender), "CentrifugeConnector/not-a-member"); gateway.collectRedeem(_poolId, _trancheId, address(msg.sender)); } @@ -420,9 +429,9 @@ contract CentrifugeConnector is Auth { // update multi-chain tranche mappings tranches[_poolId][_trancheId] = tranche; - CFGTranche storage cfgTranche = cfgTranches[_poolId][_trancheId]; - cfgTranche.poolId = _poolId; - cfgTranche.trancheId = _trancheId; + CentrifugeTranche memory cTranche = centrifugeTranches[tranche]; + cTranche.poolId = _poolId; + cTranche.trancheId = _trancheId; emit TrancheAdded(_poolId, _trancheId); } @@ -433,12 +442,11 @@ contract CentrifugeConnector is Auth { TrancheLike(token).updateTokenPrice(_price); } - function updateMember(uint64 poolId, bytes16 trancheId, address user, uint64 validUntil) public onlyGateway { - address token = tranches[_poolId][_trancheId]; - require(token != address(0), "CentrifugeConnector/invalid-pool-or-tranche"); - RestrictedTokenLike trancheToken = RestrictedTokenLike(tranche.token); - MemberlistLike memberlist = RestrictedTokenLike(token).memberlist(); - memberlist.updateMember(user, validUntil); + function updateMember(uint64 _poolId, bytes16 _trancheId, address _user, uint64 _validUntil) public onlyGateway { + TrancheLike tranche = TrancheLike(tranches[_poolId][_trancheId]); + require(address(tranche) != address(0), "CentrifugeConnector/invalid-pool-or-tranche"); + MemberlistLike memberlist = tranche.memberlist(); + memberlist.updateMember(_user, _validUntil); } function handleTransfer(uint128 currency, address recipient, uint128 amount) public onlyGateway { @@ -452,25 +460,25 @@ contract CentrifugeConnector is Auth { ); } - function handleTransferTrancheTokens(uint64 poolId, bytes16 trancheId, address destinationAddress, uint128 amount) + function handleTransferTrancheTokens(uint64 _poolId, bytes16 _trancheId, address _destinationAddress, uint128 _amount) public onlyGateway { - RestrictedTokenLike token = RestrictedTokenLike(tranches[poolId][trancheId]); - require(address(token) != address(0), "CentrifugeConnector/unknown-token"); + TrancheLike tranche = TrancheLike(tranches[_poolId][_trancheId]); + require(address(tranche) != address(0), "CentrifugeConnector/unknown-token"); - require(token.hasMember(destinationAddress), "CentrifugeConnector/not-a-member"); - token.mint(destinationAddress, amount); + require(tranche.hasMember(_destinationAddress), "CentrifugeConnector/not-a-member"); + tranche.mint(_destinationAddress, _amount); } function handleDecreaseInvestOrder(uint64 _poolId, bytes16 _trancheId, address _user, uint128 _currency, uint128 _currencyPayout, uint128 _remainingInvestOrder) public onlyGateway { require(_currencyPayout != 0, "CentrifugeConnector/zero-payout"); address currencyAddress = currencyIdToAddress[_currency]; - address tranche = tranches[_poolId][_trancheId]; - require(tranche != address(0), "CentrifugeConnector/tranche-does-not-exist"); + TrancheLike tranche = TrancheLike(tranches[_poolId][_trancheId]); + require(address(tranche) != address(0), "CentrifugeConnector/tranche-does-not-exist"); require(allowedPoolCurrencies[_poolId][currencyAddress], "CentrifugeConnector/pool-currency-not-allowed"); require(currencyAddress != address(0), "CentrifugeConnector/unknown-currency"); - require(currencyAddress == TrancheLike(tranche).asset(), "CentrifugeConnector/not-tranche-currency"); + require(currencyAddress == tranche.asset(), "CentrifugeConnector/not-tranche-currency"); // TODO: escrow should give max approval on deployment EscrowLike(escrow).approve(currencyAddress, address(this), _currencyPayout); @@ -485,10 +493,10 @@ contract CentrifugeConnector is Auth { //TODO: currency not really required here function handleDecreaseRedeemOrder(uint64 _poolId, bytes16 _trancheId, address _user, uint128 _currency, uint128 _tokensPayout, uint128 _remainingRedeemOrder) public onlyGateway { require(_tokensPayout != 0, "CentrifugeConnector/zero-payout"); - address tranche = tranches[poolId][trancheId]; - require(tranche != address(0), "CentrifugeConnector/tranche-does-not-exist"); - - require(RestrictedTokenLike(tranche).hasMember(_user), "CentrifugeConnector/not-a-member"); + TrancheLike tranche = TrancheLike(tranches[_poolId][_trancheId]); + require(address(tranche) != address(0), "CentrifugeConnector/tranche-does-not-exist"); + + require(TrancheLike(tranche).hasMember(_user), "CentrifugeConnector/not-a-member"); // TODO: escrow should give max approval on deployment EscrowLike(escrow).approve(tranche, address(this), _tokensPayout); require( @@ -503,7 +511,7 @@ contract CentrifugeConnector is Auth { address tranche = tranches[_poolId][_trancheId]; require(tranche != address(0), "CentrifugeConnector/tranche-does-not-exist"); - UserTrancheValues values = orderbook[_recepient][tranche]; + UserTrancheValues memory values = orderbook[_recepient][tranche]; values.openInvest = _remainingInvestOrder; values.maxDeposit = values.maxDeposit + _currencyInvested; values.maxMint = values.maxMint + _tokensPayout; @@ -516,7 +524,7 @@ contract CentrifugeConnector is Auth { address tranche = tranches[_poolId][_trancheId]; require(tranche != address(0), "CentrifugeConnector/tranche-does-not-exist"); - UserTrancheValues values = orderbook[_recepient][tranche]; + UserTrancheValues memory values = orderbook[_recepient][tranche]; values.openRedeem = _remainingRedeemOrder; values.maxWithdraw = values.maxWithdraw + _currencyPayout; values.maxRedeem = values.maxRedeem + _trancheTokensRedeemed; @@ -526,8 +534,6 @@ contract CentrifugeConnector is Auth { // ------ internal helper functions - //TODO: rounding - function _poolCurrencyCheck(uint64 _poolId, address _currencyAddress) internal returns (bool) { uint128 currency = currencyAddressToId[_currencyAddress]; require(currency != 0, "CentrifugeConnector/unknown-currency"); @@ -536,14 +542,14 @@ contract CentrifugeConnector is Auth { } function _trancheTokenCheck(uint64 _poolId, bytes16 _trancheId, address _user) internal returns (bool) { - RestrictedTokenLike token = RestrictedTokenLike(tranches[_poolId][_trancheId]); - require(address(token) != address(0), "CentrifugeConnector/unknown-tranche-token"); - require(token.hasMember(_user), "CentrifugeConnector/not-a-member"); + TrancheLike tranche = TrancheLike(tranches[_poolId][_trancheId]); + require(address(tranche) != address(0), "CentrifugeConnector/unknown-tranche-token"); + require(tranche.hasMember(_user), "CentrifugeConnector/not-a-member"); return true; } function _decreaseDepositLimits(address _user, address _tranche, uint256 _currency, uint256 _trancheTokens) internal { - UserTrancheValues values = orderbook[_user][_tranche]; + UserTrancheValues memory values = orderbook[_user][_tranche]; if (values.maxDeposit < _currency) { values.maxDeposit = 0; } else { @@ -557,20 +563,19 @@ contract CentrifugeConnector is Auth { } function _decreaseRedemptionLimits(address _user, address _tranche, uint256 _currency, uint256 _trancheTokens) internal { - UserTrancheValues values = orderbook[_user][_tranche]; - if (values.maxWithdrawel < _currency) { + UserTrancheValues memory values = orderbook[_user][_tranche]; + if (values.maxWithdraw < _currency) { values.maxDeposit = 0; } else { - values.maxWithdrawel = values.maxWithdrawel - _currency; + values.maxWithdraw = values.maxWithdraw - _currency; } - if (values.maxRedemption < _trancheTokens) { - values.maxRedemption = 0; + if (values.maxRedeem < _trancheTokens) { + values.maxRedeem = 0; } else { - values.maxRedemption = values.maxRedemption - _trancheTokens; + values.maxRedeem = values.maxRedeem - _trancheTokens; } } - // TODO: ward setup on tranche contract function deployTranche( uint64 _poolId, @@ -580,20 +585,20 @@ contract CentrifugeConnector is Auth { string memory _tokenName, string memory _tokenSymbol) internal returns (address) { require(tranches[_poolId][_trancheId] == address(0), "CentrifugeConnector/tranche-already-deployed"); - address token = deployTranche(_asset, address(this), _decimals, _tokenName, _tokenSymbol); // TODO: use factory - tranches[_poolId][_trancheId] = token; + address tranche_ = deployTranche(_asset, address(this), _decimals, _tokenName, _tokenSymbol); // TODO: use factory + tranches[_poolId][_trancheId] = tranche_; address memberlist = memberlistFactory.newMemberlist(); - RestrictedTokenLike(token).file("memberlist", memberlist); + TrancheLike(tranche_).file("memberlist", memberlist); MemberlistLike(memberlist).updateMember(address(escrow), type(uint256).max); // add escrow to tranche tokens memberlist - emit TrancheDeployed(poolId, trancheId, token); + emit TrancheDeployed(_poolId, _trancheId, tranche_); } // ------ EIP 4626 view functions /// @dev calculates the avg share price for the deposited assets of a specific user function calcCustomTrancheTokenPrice(address _user, address _tranche) public view returns (uint256 userTrancheTokenPrice) { - UserTrancheValues values = orderbook[_user][_tranche]; + UserTrancheValues memory values = orderbook[_user][_tranche]; if(values.maxMint == 0) { return 0; } diff --git a/src/Tranche4626.sol b/src/Tranche4626.sol index 1f443029..60024e43 100644 --- a/src/Tranche4626.sol +++ b/src/Tranche4626.sol @@ -51,7 +51,7 @@ contract Tranche4626 is RestrictedToken { event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares); event Withdraw(address indexed sender, address indexed receiver, address indexed owner, uint256 assets, uint256 shares); - constructor(address _asset, address _connector, uint8 _decimals, string memory _name, string memory _symbol) super(decimals) { + constructor(address _asset, address _connector, uint8 _decimals, string memory _name, string memory _symbol) RestrictedToken(_decimals) { name = _name; symbol = _symbol; asset = _asset; @@ -84,7 +84,7 @@ contract Tranche4626 is RestrictedToken { return connector.maxDeposit(_receiver, address(this)); } - /// @return The amount of shares that any user would get for an amount of assets provided -> convertToShares + /// @return shares that any user would get for an amount of assets provided -> convertToShares function previewDeposit(uint256 _assets) public view returns (uint256 shares) { shares = convertToShares(_assets); } @@ -113,7 +113,7 @@ contract Tranche4626 is RestrictedToken { return connector.maxMint(_receiver, address(this)); } - /// @return The amount of assets that any user would get for an amount of shares provided -> convertToAssets + /// @return assets that any user would get for an amount of shares provided -> convertToAssets function previewMint(uint256 _shares) external view returns (uint256 assets) { assets = convertToAssets(_shares); } @@ -123,13 +123,12 @@ contract Tranche4626 is RestrictedToken { connector.requestRedeem(address(this), _receiver, _shares); } - /// @dev - /// @return + /// @return maxAssets that the receiver can withdraw function maxWithdraw(address _receiver) public view returns (uint256 maxAssets) { return connector.maxWithdraw(_receiver, address(this)); } - /// @return The amount of shares a user would need to redeem in order to receive the given amount of assets -> convertToAssets + /// @return shares that a user would need to redeem in order to receive the given amount of assets -> convertToAssets function previewWithdraw(uint256 _assets) public view returns (uint256 shares) { shares = convertToShares(_assets); } @@ -148,7 +147,7 @@ contract Tranche4626 is RestrictedToken { } - /// @return The amount of assets that any user could redeem for an given amount of shares -> convertToAssets + /// @return assets that any user could redeem for an given amount of shares -> convertToAssets function previewRedeem(uint256 _shares) public view returns (uint256 assets) { assets = convertToAssets(_shares); } diff --git a/src/routers/Gateway.sol b/src/routers/Gateway.sol index b021520c..b70a0b8a 100644 --- a/src/routers/Gateway.sol +++ b/src/routers/Gateway.sol @@ -211,23 +211,7 @@ contract ConnectorGateway { (uint64 poolId, bytes16 trancheId, address destinationAddress, uint128 amount) = ConnectorMessages.parseTransferTrancheTokens20(_msg); connector.handleTransferTrancheTokens(poolId, trancheId, destinationAddress, amount); - } else if (ConnectorMessages.isCollectRedeem(_msg)) { - (uint64 poolId, bytes16 trancheId, address destinationAddress, uint128 currency, uint128 currencyPayout, uint128 tokensRedeemed, uint128 remainingRedeemOrder) = - ConnectorMessages.parseCollectRedeem(_msg); - connector.handleCollectRedeem(poolId, trancheId, destinationAddress, currency, currencyPayout, tokensRedeemed, remainingRedeemOrder); - } else if (ConnectorMessages.isCollectInvest(_msg)) { - (uint64 poolId, bytes16 trancheId, address destinationAddress, uint128 currency, uint128 currencyInvested, uint128 tokensPayout, uint128 remainingInvestOrder) = - ConnectorMessages.parseCollectInvest(_msg); - connector.handleCollectInvest(poolId, trancheId, destinationAddress, currency, currencyInvested, tokensPayout, remainingInvestOrder); - } else if (ConnectorMessages.isDecreaseInvestOrder(_msg)) { - (uint64 poolId, bytes16 trancheId, address destinationAddress, uint128 currency, uint128 currencyPayout, uint128 remainingInvestOrder) = - ConnectorMessages.parseDecreaseInvestOrder(_msg); - connector.handleDecreaseInvestOrder(poolId, trancheId, destinationAddress, currency, currencyPayout, remainingInvestOrder); - } else if (ConnectorMessages.isDecreaseRedeemOrder(_msg)) { - (uint64 poolId, bytes16 trancheId, address destinationAddress, uint128 currency, uint128 trancheTokensPayout, uint128 remainingRedeemOrder) = - ConnectorMessages.parseDecreaseRedeemOrder(_msg); - connector.handleDecreaseRedeemOrder(poolId, trancheId, destinationAddress, currency, trancheTokensPayout, remainingRedeemOrder); - } + } else { revert("ConnectorGateway/invalid-message"); } diff --git a/src/token/restricted.sol b/src/token/restricted.sol index ce64a377..b208b24e 100644 --- a/src/token/restricted.sol +++ b/src/token/restricted.sol @@ -33,7 +33,7 @@ contract RestrictedToken is ERC20 { // --- Events --- event File(bytes32 indexed what, address data); - constructor(uint8 decimals_) ERC20(decimals_) {} + constructor(uint8 decimals_) ERC20(decimals_) { } modifier checkMember(address user) { memberlist.member(user); From e18ac1ccc7ec6d2d69691654123325c53f2d6e54 Mon Sep 17 00:00:00 2001 From: ilinzweilin Date: Thu, 22 Jun 2023 22:11:40 +0100 Subject: [PATCH 12/24] fix build --- src/Connector.sol | 97 +-- src/Tranche4626.sol | 36 +- test/Connector.t.sol | 1474 +++++++++++++++++++++--------------------- 3 files changed, 771 insertions(+), 836 deletions(-) diff --git a/src/Connector.sol b/src/Connector.sol index 015f9151..b1f8fda2 100644 --- a/src/Connector.sol +++ b/src/Connector.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.18; pragma abicoder v2; import {TrancheTokenFactoryLike, MemberlistFactoryLike} from "./token/factory.sol"; +import {Tranche4626} from "./Tranche4626.sol"; import {ERC20Like} from "./token/restricted.sol"; import {MemberlistLike} from "./token/memberlist.sol"; import "./auth/auth.sol"; @@ -295,75 +296,6 @@ contract CentrifugeConnector is Auth { ); } - // function increaseInvestOrder(uint64 _poolId, bytes16 _trancheId, address _currencyAddress, uint128 _amount ) public { - // _increaseInvestOrder(_poolId, _trancheId, _currencyAddress, _amount, msg.sender); - // } - - // function _increaseInvestOrder(uint64 _poolId, bytes16 _trancheId, address _currencyAddress, uint128 _amount, address _user) internal { - // RestrictedTokenLike token = RestrictedTokenLike(tranches[_poolId][_trancheId]); - // require(address(token) != address(0), "CentrifugeConnector/unknown-tranche-token"); - // require(token.hasMember(_user), "CentrifugeConnector/not-a-member"); - - // uint128 currency = currencyAddressToId[_currencyAddress]; - // require(currency != 0, "CentrifugeConnector/unknown-currency"); - // require(allowedPoolCurrencies[_poolId][_currencyAddress], "CentrifugeConnector/pool-currency-not-allowed"); - - // require( - // ERC20Like(_currencyAddress).transferFrom(_user, address(escrow), _amount), - // "Centrifuge/Connector/currency-transfer-failed" - // ); - - // gateway.increaseInvestOrder(_poolId, _trancheId, _user, currency, _amount); - // } - - // function decreaseInvestOrder(uint64 _poolId, bytes16 _trancheId, address _currencyAddress, uint128 _amount) public { - // _decreaseInvestOrder(_poolId, _trancheId, _currencyAddress, _amount, msg.sender); - // } - - // function _decreaseInvestOrder(uint64 poolId, bytes16 _trancheId, address _currencyAddress, uint128 _amount, address _user) public { - // // RestrictedTokenLike token = RestrictedTokenLike(tranches[poolId][_trancheId].token); - // // require(address(token) != address(0), "CentrifugeConnector/unknown-tranche-token"); - // // require(token.hasMember(_user), "CentrifugeConnector/not-a-member"); - - // uint128 currency = currencyAddressToId[_currencyAddress]; - // require(currency != 0, "CentrifugeConnector/unknown-currency"); - // require(allowedPoolCurrencies[poolId][_currencyAddress], "CentrifugeConnector/pool-currency-not-allowed"); - - // gateway.decreaseInvestOrder(poolId, _trancheId, _user, currency, _amount); - // } - - // function increaseRedeemOrder(uint64 _poolId, bytes16 _trancheId, address _currencyAddress, uint128 _amount) public { - // _increaseRedeemOrder(_poolId, _trancheId, _currencyAddress, _amount, msg.sender); - // } - - // function _increaseRedeemOrder(uint64 _poolId, bytes16 _trancheId, address _currencyAddress, uint128 _amount, address _user) internal { - // RestrictedTokenLike token = RestrictedTokenLike(tranches[_poolId][_trancheId]); - // require(address(token) != address(0), "CentrifugeConnector/unknown-tranche-token"); - // require(token.hasMember(_user), "CentrifugeConnector/not-a-member"); - - // uint128 currency = currencyAddressToId[_currencyAddress]; - // require(currency != 0, "CentrifugeConnector/unknown-currency"); - // require(allowedPoolCurrencies[_poolId][_currencyAddress], "CentrifugeConnector/pool-currency-not-allowed"); - - // gateway.increaseRedeemOrder(_poolId, _trancheId, _user, currency, _amount); - // } - - // function decreaseRedeemOrder(uint64 _poolId, bytes16 _trancheId, address _currencyAddress, uint128 _amount) public { - // _decreaseRedeemOrder(_poolId, _trancheId, _currencyAddress, _amount, msg.sender); - // } - - // function _decreaseRedeemOrder(uint64 _poolId, bytes16 _trancheId, address _currencyAddress, uint128 _amount, address _user) internal { - // RestrictedTokenLike token = RestrictedTokenLike(tranches[_poolId][_trancheId]); - // require(address(token) != address(0), "CentrifugeConnector/unknown-tranche-token"); - // require(token.hasMember(_user), "CentrifugeConnector/not-a-member"); - - // uint128 currency = currencyAddressToId[_currencyAddress]; - // require(currency != 0, "CentrifugeConnector/unknown-currency"); - // require(allowedPoolCurrencies[_poolId][_currencyAddress], "CentrifugeConnector/pool-currency-not-allowed"); - - // gateway.decreaseRedeemOrder(_poolId, _trancheId, _user, currency, _amount); - // } - function collectInvest(uint64 _poolId, bytes16 _trancheId) public { TrancheLike tranche = TrancheLike(tranches[_poolId][_trancheId]); require(address(tranche) != address(0), "CentrifugeConnector/unknown-tranche-token"); @@ -424,7 +356,7 @@ contract CentrifugeConnector is Auth { address tranche = tranches[_poolId][_trancheId]; require(tranche == address(0), "CentrifugeConnector/tranche-already-added"); address asset = address(0x6B175474E89094C44Da98b954EedeAC495271d0F); // TODO FIX : provide tranche currency / assets : default DAI? - tranche = deployTranche(_poolId, _trancheId, asset, _tokenName, _tokenSymbol, _decimals); + tranche = deployTranche(_poolId, _trancheId, asset, _decimals, _tokenName, _tokenSymbol); TrancheLike(tranche).updateTokenPrice(_price); // update multi-chain tranche mappings @@ -445,7 +377,7 @@ contract CentrifugeConnector is Auth { function updateMember(uint64 _poolId, bytes16 _trancheId, address _user, uint64 _validUntil) public onlyGateway { TrancheLike tranche = TrancheLike(tranches[_poolId][_trancheId]); require(address(tranche) != address(0), "CentrifugeConnector/invalid-pool-or-tranche"); - MemberlistLike memberlist = tranche.memberlist(); + MemberlistLike memberlist = MemberlistLike(tranche.memberlist()); memberlist.updateMember(_user, _validUntil); } @@ -487,7 +419,7 @@ contract CentrifugeConnector is Auth { ERC20Like(currencyAddress).transferFrom(address(escrow), _user, _currencyPayout), "CentrifugeConnector/currency-transfer-failed" ); - orderbook[_user][tranche].openInvest = _remainingInvestOrder; + orderbook[_user][address(tranche)].openInvest = _remainingInvestOrder; } //TODO: currency not really required here @@ -498,12 +430,12 @@ contract CentrifugeConnector is Auth { require(TrancheLike(tranche).hasMember(_user), "CentrifugeConnector/not-a-member"); // TODO: escrow should give max approval on deployment - EscrowLike(escrow).approve(tranche, address(this), _tokensPayout); + EscrowLike(escrow).approve(address(tranche), address(this), _tokensPayout); require( - ERC20Like(tranche).transferFrom(address(escrow), _user, _tokensPayout), + tranche.transferFrom(address(escrow), _user, _tokensPayout), "CentrifugeConnector/trancheTokens-transfer-failed" ); - orderbook[_user][tranche].openRedeem = _remainingRedeemOrder; + orderbook[_user][address(tranche)].openRedeem = _remainingRedeemOrder; } function handleCollectInvest(uint64 _poolId, bytes16 _trancheId, address _recepient, uint128 _currency, uint128 _currencyInvested, uint128 _tokensPayout, uint128 _remainingInvestOrder) public onlyGateway { @@ -534,7 +466,7 @@ contract CentrifugeConnector is Auth { // ------ internal helper functions - function _poolCurrencyCheck(uint64 _poolId, address _currencyAddress) internal returns (bool) { + function _poolCurrencyCheck(uint64 _poolId, address _currencyAddress) internal view returns (bool) { uint128 currency = currencyAddressToId[_currencyAddress]; require(currency != 0, "CentrifugeConnector/unknown-currency"); require(allowedPoolCurrencies[_poolId][_currencyAddress], "CentrifugeConnector/pool-currency-not-allowed"); @@ -549,7 +481,7 @@ contract CentrifugeConnector is Auth { } function _decreaseDepositLimits(address _user, address _tranche, uint256 _currency, uint256 _trancheTokens) internal { - UserTrancheValues memory values = orderbook[_user][_tranche]; + UserTrancheValues storage values = orderbook[_user][_tranche]; if (values.maxDeposit < _currency) { values.maxDeposit = 0; } else { @@ -563,7 +495,7 @@ contract CentrifugeConnector is Auth { } function _decreaseRedemptionLimits(address _user, address _tranche, uint256 _currency, uint256 _trancheTokens) internal { - UserTrancheValues memory values = orderbook[_user][_tranche]; + UserTrancheValues storage values = orderbook[_user][_tranche]; if (values.maxWithdraw < _currency) { values.maxDeposit = 0; } else { @@ -575,9 +507,9 @@ contract CentrifugeConnector is Auth { values.maxRedeem = values.maxRedeem - _trancheTokens; } } - + // TODO: ward setup on tranche contract - function deployTranche( + function deployTranche( uint64 _poolId, bytes16 _trancheId, address _asset, @@ -585,13 +517,15 @@ contract CentrifugeConnector is Auth { string memory _tokenName, string memory _tokenSymbol) internal returns (address) { require(tranches[_poolId][_trancheId] == address(0), "CentrifugeConnector/tranche-already-deployed"); - address tranche_ = deployTranche(_asset, address(this), _decimals, _tokenName, _tokenSymbol); // TODO: use factory + Tranche4626 tranche = new Tranche4626(_asset, address(this), _decimals, _tokenName, _tokenSymbol); // TODO: use factory + address tranche_ = address(tranche); tranches[_poolId][_trancheId] = tranche_; address memberlist = memberlistFactory.newMemberlist(); TrancheLike(tranche_).file("memberlist", memberlist); MemberlistLike(memberlist).updateMember(address(escrow), type(uint256).max); // add escrow to tranche tokens memberlist emit TrancheDeployed(_poolId, _trancheId, tranche_); + return tranche_; } // ------ EIP 4626 view functions @@ -618,6 +552,7 @@ contract CentrifugeConnector is Auth { } function maxRedeem(address _user, address _tranche) public view returns (uint256) { + // UserTrancheValues v = orderbook[_user][_tranche]; return orderbook[_user][_tranche].maxRedeem; } } diff --git a/src/Tranche4626.sol b/src/Tranche4626.sol index 60024e43..1e7bfd9d 100644 --- a/src/Tranche4626.sol +++ b/src/Tranche4626.sol @@ -26,12 +26,12 @@ interface ConnectorLike { function processDeposit(address _tranche, address _receiver, uint256 _assets) external returns (uint256); function processMint(address _tranche, address _receiver, uint256 _shares) external returns (uint256); function processWithdraw(address _tranche, uint256 _assets, address _receiver, address _owner) external returns (uint256); - function maxDeposit(address _user, address _tranche) external returns (uint256); - function maxMint(address _user, address _tranche) external returns (uint256); - function maxWithdraw(address _user, address _tranche) external returns (uint256); - function maxRedeem(address _user, address _tranche) external returns (uint256); - function requestRedeem(uint256 _shares, address _receiver) external; - function requestDeposit(uint256 _assets, address _receiver) external; + function maxDeposit(address _user, address _tranche) external view returns (uint256); + function maxMint(address _user, address _tranche) external view returns (uint256); + function maxWithdraw(address _user, address _tranche) external view returns (uint256); + function maxRedeem(address _user, address _tranche) external view returns (uint256); + function requestRedeem(address _tranche, uint256 _shares, address _receiver) external; + function requestDeposit(address _tranche, uint256 _assets, address _receiver) external; } @@ -64,7 +64,7 @@ contract Tranche4626 is RestrictedToken { /// @dev The total amount of vault shares. /// @return Total amount of the underlying vault assets including accrued interest. function totalAssets() public view returns (uint256) { - return totalSupply() * latestPrice; + return totalSupply * latestPrice; } /// @dev Calculates the amount of shares / tranche tokens that any user would get for the amount of assets provided. The calcultion is based on the token price from the most recent epoch retrieved from Centrifuge chain. @@ -91,26 +91,26 @@ contract Tranche4626 is RestrictedToken { /// @dev request asset deposit for a receiver to be included in the next epoch execution. Asset is locked in the escrow on request submission. function requestDeposit(uint256 _assets, address _receiver) auth public { - connector.requestDeposit(address(this), _receiver, _assets); + connector.requestDeposit(address(this), _assets, _receiver); } /// @dev collect shares for deposited funds after pool epoch execution. maxMint is the max amount of shares that can be collected. Required assets must already be locked. /// maxDeposit is the amount of funds that was successfully invested into the pool on Centrifuge chain function deposit(uint256 _assets, address _receiver) auth public returns (uint256 shares) { - uint transferredShares = connector.processDeposit(address(this), _receiver, _assets); - Deposit(address(this), _receiver, _assets, transferredShares); + shares = connector.processDeposit(address(this), _receiver, _assets); + emit Deposit(address(this), _receiver, _assets, shares); } /// @dev collect shares for deposited funds after pool epoch execution. maxMint is the max amount of shares that can be collected. Required assets must already be locked. /// maxDeposit is the amount of funds that was successfully invested into the pool on Centrifuge chain function mint(uint256 _shares, address _receiver) auth public returns (uint256 assets) { - uint lockedAssets = connector.processMint(address(this), _receiver, _shares); - Deposit(address(this), _receiver, lockedAssets, _shares); + assets = connector.processMint(address(this), _receiver, _shares); + emit Deposit(address(this), _receiver, assets, _shares); } /// @dev Maximum amount of shares that can be claimed by the receiver after the epoch has been executed on the Centrifuge chain side. function maxMint(address _receiver) external view returns (uint256 maxShares) { - return connector.maxMint(_receiver, address(this)); + maxShares = connector.maxMint(_receiver, address(this)); } /// @return assets that any user would get for an amount of shares provided -> convertToAssets @@ -120,7 +120,7 @@ contract Tranche4626 is RestrictedToken { /// @dev request share redemption for a receiver to be included in the next epoch execution. Shares are locked in the escrow on request submission. function requestRedeem(uint256 _shares, address _receiver) auth public { - connector.requestRedeem(address(this), _receiver, _shares); + connector.requestRedeem(address(this), _shares, _receiver); } /// @return maxAssets that the receiver can withdraw @@ -137,13 +137,13 @@ contract Tranche4626 is RestrictedToken { /// @return shares that have been redeemed for the excat _assets amount function withdraw(uint256 _assets, address _receiver, address _owner) auth public returns (uint256 shares) { uint sharesRedeemed = connector.processWithdraw(address(this), _assets, _receiver, _owner); - Withdraw(address(this), _receiver, _owner, _assets, sharesRedeemed); + emit Withdraw(address(this), _receiver, _owner, _assets, sharesRedeemed); return sharesRedeemed; } /// @dev Max amount of shares that can be redeemed by the owner after redemption was requested - function maxRedeem(address owner) public view returns (uint256 maxShares) { - return connector.maxRedeem(owner, address(this)); + function maxRedeem(address _owner) public view returns (uint256 maxShares) { + return connector.maxRedeem(_owner, address(this)); } @@ -156,7 +156,7 @@ contract Tranche4626 is RestrictedToken { /// @return assets currency payout for the exact amount of redeemed _shares function redeem(uint256 _shares, address _receiver, address _owner) auth public returns (uint256 assets) { uint currencyPayout = connector.processWithdraw(address(this), _shares, _receiver, _owner); - Withdraw(address(this), _receiver, _owner, currencyPayout, _shares); + emit Withdraw(address(this), _receiver, _owner, currencyPayout, _shares); return currencyPayout; } diff --git a/test/Connector.t.sol b/test/Connector.t.sol index 45ae68b6..335ce9ed 100644 --- a/test/Connector.t.sol +++ b/test/Connector.t.sol @@ -43,743 +43,743 @@ contract ConnectorTest is Test { mockXcmRouter.file("gateway", address(gateway)); } - function testAddCurrencyWorks(uint128 currency, uint128 badCurrency) public { - vm.assume(currency > 0); - vm.assume(badCurrency > 0); - vm.assume(currency != badCurrency); - - ERC20 erc20 = newErc20("X's Dollar", "USDX", 42); - connector.addCurrency(currency, address(erc20)); - (address address_) = bridgedConnector.currencyIdToAddress(currency); - assertEq(address_, address(erc20)); - - // Verify we can't override the same currency id another address - ERC20 badErc20 = newErc20("BadActor's Dollar", "BADUSD", 66); - vm.expectRevert(bytes("CentrifugeConnector/currency-id-in-use")); - connector.addCurrency(currency, address(badErc20)); - assertEq(bridgedConnector.currencyIdToAddress(currency), address(erc20)); - - // Verify we can't add a currency address that already exists associated with a different currency id - vm.expectRevert(bytes("CentrifugeConnector/currency-address-in-use")); - connector.addCurrency(badCurrency, address(erc20)); - assertEq(bridgedConnector.currencyIdToAddress(currency), address(erc20)); - } - - function testAddPoolWorks(uint64 poolId) public { - connector.addPool(poolId); - (uint64 actualPoolId,) = bridgedConnector.pools(poolId); - assertEq(uint256(actualPoolId), uint256(poolId)); - } - - function testAllowPoolCurrencyWorks(uint128 currency, uint64 poolId) public { - ERC20 token = newErc20("X's Dollar", "USDX", 42); - connector.addCurrency(currency, address(token)); - connector.addPool(poolId); - - connector.allowPoolCurrency(poolId, currency); - assertTrue(bridgedConnector.allowedPoolCurrencies(poolId, address(token))); - } - - function testAllowPoolCurrencyWithUnknownCurrencyFails(uint128 currency, uint64 poolId) public { - connector.addPool(poolId); - vm.expectRevert(bytes("CentrifugeConnector/unknown-currency")); - connector.allowPoolCurrency(poolId, currency); - } - - function testAddingPoolMultipleTimesFails(uint64 poolId) public { - connector.addPool(poolId); - - vm.expectRevert(bytes("CentrifugeConnector/pool-already-added")); - connector.addPool(poolId); - } - - function testAddingPoolAsNonRouterFails(uint64 poolId) public { - vm.expectRevert(bytes("CentrifugeConnector/not-the-gateway")); - bridgedConnector.addPool(poolId); - } - - function testAddingSingleTrancheWorks( - uint64 poolId, - bytes16 trancheId, - string memory tokenName, - string memory tokenSymbol, - uint8 decimals, - uint128 price - ) public { - connector.addPool(poolId); - (uint64 actualPoolId,) = bridgedConnector.pools(poolId); - assertEq(uint256(actualPoolId), uint256(poolId)); - - connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); - bridgedConnector.deployTranche(poolId, trancheId); - - ( - address token_, - uint256 latestPrice, - , - string memory actualTokenName, - string memory actualTokenSymbol, - uint8 actualDecimals - ) = bridgedConnector.tranches(poolId, trancheId); - assertTrue(token_ != address(0)); - assertEq(latestPrice, price); - - // Comparing raw input to output can erroneously fail when a byte string is given. - // Intended behaviour is that byte strings will be treated as bytes and converted to strings - // instead of treated as strings themselves. This conversion from string to bytes32 to string - // is used to simulate this intended behaviour. - assertEq(actualTokenName, bytes32ToString(stringToBytes32(tokenName))); - assertEq(actualTokenSymbol, bytes32ToString(stringToBytes32(tokenSymbol))); - assertEq(actualDecimals, decimals); - - RestrictedTokenLike token = RestrictedTokenLike(token_); - assertEq(token.name(), bytes32ToString(stringToBytes32(tokenName))); - assertEq(token.symbol(), bytes32ToString(stringToBytes32(tokenSymbol))); - assertEq(token.decimals(), decimals); - } - - function testAddingTrancheMultipleTimesFails( - uint64 poolId, - uint8 decimals, - string memory tokenName, - string memory tokenSymbol, - bytes16 trancheId, - uint128 price - ) public { - connector.addPool(poolId); - connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); - - vm.expectRevert(bytes("CentrifugeConnector/tranche-already-added")); - connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); - } - - function testAddingMultipleTranchesWorks( - uint64 poolId, - bytes16[] calldata trancheIds, - string memory tokenName, - string memory tokenSymbol, - uint8 decimals, - uint128 price - ) public { - vm.assume(trancheIds.length > 0 && trancheIds.length < 5); - vm.assume(!hasDuplicates(trancheIds)); - connector.addPool(poolId); - - for (uint256 i = 0; i < trancheIds.length; i++) { - connector.addTranche(poolId, trancheIds[i], tokenName, tokenSymbol, decimals, price); - bridgedConnector.deployTranche(poolId, trancheIds[i]); - (address token, uint256 latestPrice,,,, uint8 actualDecimals) = - bridgedConnector.tranches(poolId, trancheIds[i]); - assertEq(latestPrice, price); - assertTrue(token != address(0)); - assertEq(actualDecimals, decimals); - } - } - - function testAddingTranchesAsNonRouterFails( - uint64 poolId, - bytes16 trancheId, - string memory tokenName, - string memory tokenSymbol, - uint8 decimals, - uint128 price - ) public { - connector.addPool(poolId); - vm.expectRevert(bytes("CentrifugeConnector/not-the-gateway")); - bridgedConnector.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); - } - - function testAddingTranchesForNonExistentPoolFails( - uint64 poolId, - bytes16 trancheId, - string memory tokenName, - string memory tokenSymbol, - uint8 decimals, - uint128 price - ) public { - vm.expectRevert(bytes("CentrifugeConnector/invalid-pool")); - connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); - } - - function testDeployingTrancheMultipleTimesFails( - uint64 poolId, - uint8 decimals, - string memory tokenName, - string memory tokenSymbol, - bytes16 trancheId, - uint128 price - ) public { - connector.addPool(poolId); - connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); - bridgedConnector.deployTranche(poolId, trancheId); - - vm.expectRevert(bytes("CentrifugeConnector/tranche-already-deployed")); - bridgedConnector.deployTranche(poolId, trancheId); - } - - function testDeployingWrongTrancheFails( - uint64 poolId, - uint8 decimals, - string memory tokenName, - string memory tokenSymbol, - bytes16 trancheId, - bytes16 wrongTrancheId, - uint128 price - ) public { - vm.assume(trancheId != wrongTrancheId); - - connector.addPool(poolId); - (uint64 actualPoolId,) = bridgedConnector.pools(poolId); - assertEq(uint256(actualPoolId), uint256(poolId)); - - connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); - vm.expectRevert(bytes("CentrifugeConnector/invalid-pool-or-tranche")); - bridgedConnector.deployTranche(poolId, wrongTrancheId); - } - - function testDeployingTrancheOnNonExistentPoolFails( - uint64 poolId, - uint8 decimals, - uint64 wrongPoolId, - string memory tokenName, - string memory tokenSymbol, - bytes16 trancheId, - uint128 price - ) public { - vm.assume(poolId != wrongPoolId); - - connector.addPool(poolId); - (uint64 actualPoolId,) = bridgedConnector.pools(poolId); - assertEq(uint256(actualPoolId), uint256(poolId)); - - connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); - vm.expectRevert(bytes("CentrifugeConnector/invalid-pool-or-tranche")); - bridgedConnector.deployTranche(wrongPoolId, trancheId); - } - - function testUpdatingMemberWorks(uint64 poolId, uint8 decimals, bytes16 trancheId, address user, uint64 validUntil) - public - { - vm.assume(validUntil >= block.timestamp); - vm.assume(user != address(0)); - - connector.addPool(poolId); - connector.addTranche(poolId, trancheId, "Some Name", "SYMBOL", decimals, 123); - bridgedConnector.deployTranche(poolId, trancheId); - connector.updateMember(poolId, trancheId, user, validUntil); - - (address token_,,,,,) = bridgedConnector.tranches(poolId, trancheId); - RestrictedTokenLike token = RestrictedTokenLike(token_); - assertTrue(token.hasMember(user)); - - MemberlistLike memberlist = MemberlistLike(token.memberlist()); - assertEq(memberlist.members(user), validUntil); - } - - function testUpdatingMemberAsNonRouterFails(uint64 poolId, bytes16 trancheId, address user, uint64 validUntil) - public - { - vm.assume(validUntil <= block.timestamp); - vm.assume(user != address(0)); - - vm.expectRevert(bytes("CentrifugeConnector/not-the-gateway")); - bridgedConnector.updateMember(poolId, trancheId, user, validUntil); - } - - function testUpdatingMemberForNonExistentPoolFails( - uint64 poolId, - bytes16 trancheId, - address user, - uint64 validUntil - ) public { - vm.assume(validUntil > block.timestamp); - bridgedConnector.file("gateway", address(this)); - vm.expectRevert(bytes("CentrifugeConnector/invalid-pool-or-tranche")); - bridgedConnector.updateMember(poolId, trancheId, user, validUntil); - } - - function testUpdatingMemberForNonExistentTrancheFails( - uint64 poolId, - bytes16 trancheId, - address user, - uint64 validUntil - ) public { - vm.assume(validUntil > block.timestamp); - connector.addPool(poolId); - - vm.expectRevert(bytes("CentrifugeConnector/invalid-pool-or-tranche")); - connector.updateMember(poolId, trancheId, user, validUntil); - } - - function testUpdatingTokenPriceWorks(uint64 poolId, uint8 decimals, bytes16 trancheId, uint128 price) public { - connector.addPool(poolId); - connector.addTranche(poolId, trancheId, "Some Name", "SYMBOL", decimals, 123); - connector.updateTokenPrice(poolId, trancheId, price); - - (, uint256 latestPrice, uint256 lastPriceUpdate,,,) = bridgedConnector.tranches(poolId, trancheId); - assertEq(latestPrice, price); - assertEq(lastPriceUpdate, block.timestamp); - } - - function testUpdatingTokenPriceAsNonRouterFails(uint64 poolId, uint8 decimals, bytes16 trancheId, uint128 price) - public - { - connector.addPool(poolId); - connector.addTranche(poolId, trancheId, "Some Name", "SYMBOL", decimals, 123); - vm.expectRevert(bytes("CentrifugeConnector/not-the-gateway")); - bridgedConnector.updateTokenPrice(poolId, trancheId, price); - } - - function testUpdatingTokenPriceForNonExistentPoolFails(uint64 poolId, bytes16 trancheId, uint128 price) public { - bridgedConnector.file("gateway", address(this)); - vm.expectRevert(bytes("CentrifugeConnector/invalid-pool-or-tranche")); - bridgedConnector.updateTokenPrice(poolId, trancheId, price); - } - - function testUpdatingTokenPriceForNonExistentTrancheFails(uint64 poolId, bytes16 trancheId, uint128 price) public { - connector.addPool(poolId); - - vm.expectRevert(bytes("CentrifugeConnector/invalid-pool-or-tranche")); - connector.updateTokenPrice(poolId, trancheId, price); - } - - function testIncomingTransferWithoutEscrowFundsFails( - string memory tokenName, - string memory tokenSymbol, - uint8 decimals, - uint128 currency, - bytes32 sender, - address recipient, - uint128 amount - ) public { - vm.assume(decimals > 0); - vm.assume(amount > 0); - vm.assume(recipient != address(0)); - - ERC20 erc20 = newErc20(tokenName, tokenSymbol, decimals); - connector.addCurrency(currency, address(erc20)); - - assertEq(erc20.balanceOf(address(bridgedConnector.escrow())), 0); - vm.expectRevert(bytes("ERC20/insufficient-balance")); - connector.incomingTransfer(currency, sender, bytes32(bytes20(recipient)), amount); - assertEq(erc20.balanceOf(address(bridgedConnector.escrow())), 0); - assertEq(erc20.balanceOf(recipient), 0); - } - - function testIncomingTransferWorks( - string memory tokenName, - string memory tokenSymbol, - uint8 decimals, - uint128 currency, - bytes32 sender, - address recipient, - uint128 amount - ) public { - vm.assume(decimals > 0); - vm.assume(amount > 0); - vm.assume(currency != 0); - vm.assume(recipient != address(0)); - - ERC20 erc20 = newErc20(tokenName, tokenSymbol, decimals); - connector.addCurrency(currency, address(erc20)); - - // First, an outgoing transfer must take place which has funds currency of the currency moved to - // the escrow account, from which funds are moved from into the recipient on an incoming transfer. - erc20.approve(address(bridgedConnector), type(uint256).max); - erc20.mint(address(this), amount); - bridgedConnector.transfer(address(erc20), bytes32(bytes20(recipient)), amount); - assertEq(erc20.balanceOf(address(bridgedConnector.escrow())), amount); - - // Now we test the incoming message - connector.incomingTransfer(currency, sender, bytes32(bytes20(recipient)), amount); - assertEq(erc20.balanceOf(address(bridgedConnector.escrow())), 0); - assertEq(erc20.balanceOf(recipient), amount); - } - - // Verify that funds are moved from the msg.sender into the escrow account - function testOutgoingTransferWorks( - string memory tokenName, - string memory tokenSymbol, - uint8 decimals, - uint128 initialBalance, - uint128 currency, - bytes32 recipient, - uint128 amount - ) public { - vm.assume(decimals > 0); - vm.assume(amount > 0); - vm.assume(currency != 0); - vm.assume(initialBalance >= amount); - - ERC20 erc20 = newErc20(tokenName, tokenSymbol, decimals); - - vm.expectRevert(bytes("CentrifugeConnector/unknown-currency")); - bridgedConnector.transfer(address(erc20), recipient, amount); - connector.addCurrency(currency, address(erc20)); - - erc20.mint(address(this), initialBalance); - assertEq(erc20.balanceOf(address(this)), initialBalance); - assertEq(erc20.balanceOf(address(bridgedConnector.escrow())), 0); - erc20.approve(address(bridgedConnector), type(uint256).max); - - bridgedConnector.transfer(address(erc20), recipient, amount); - assertEq(erc20.balanceOf(address(this)), initialBalance - amount); - assertEq(erc20.balanceOf(address(bridgedConnector.escrow())), amount); - } - // Test transferring `amount` to the address(this)'s account (Centrifuge Chain -> EVM like) and then try - // transferring that amount to a `centChainAddress` (EVM -> Centrifuge Chain like). - - function testTransferTrancheTokensToCentrifuge( - uint64 poolId, - uint8 decimals, - string memory tokenName, - string memory tokenSymbol, - bytes16 trancheId, - uint128 price, - bytes32 centChainAddress, - uint128 amount, - uint64 validUntil - ) public { - vm.assume(validUntil > block.timestamp + 7 days); - connector.addPool(poolId); - connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); - bridgedConnector.deployTranche(poolId, trancheId); - connector.updateMember(poolId, trancheId, address(this), validUntil); - - // fund this account with amount - connector.incomingTransferTrancheTokens(poolId, trancheId, uint64(block.chainid), address(this), amount); - - // Verify the address(this) has the expected amount - (address tokenAddress,,,,,) = bridgedConnector.tranches(poolId, trancheId); - RestrictedTokenLike token = RestrictedTokenLike(tokenAddress); - assertEq(token.balanceOf(address(this)), amount); - - // Now send the transfer from EVM -> Cent Chain - token.approve(address(bridgedConnector), amount); - bridgedConnector.transferTrancheTokensToCentrifuge(poolId, trancheId, centChainAddress, amount); - assertEq(token.balanceOf(address(this)), 0); - - // Finally, verify the connector called `router.send` - bytes memory message = ConnectorMessages.formatTransferTrancheTokens( - poolId, - trancheId, - bytes32(bytes20(address(this))), - ConnectorMessages.formatDomain(ConnectorMessages.Domain.Centrifuge), - centChainAddress, - amount - ); - assertEq(mockXcmRouter.sentMessages(message), true); - } - - function testTransferTrancheTokensFromCentrifuge( - uint64 poolId, - bytes16 trancheId, - string memory tokenName, - string memory tokenSymbol, - uint8 decimals, - uint128 price, - uint64 validUntil, - address destinationAddress, - uint128 amount - ) public { - vm.assume(validUntil > block.timestamp + 7 days); - vm.assume(destinationAddress != address(0)); - - connector.addPool(poolId); - connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); - bridgedConnector.deployTranche(poolId, trancheId); - connector.updateMember(poolId, trancheId, destinationAddress, validUntil); - - connector.incomingTransferTrancheTokens(poolId, trancheId, uint64(block.chainid), destinationAddress, amount); - (address token,,,,,) = bridgedConnector.tranches(poolId, trancheId); - assertEq(ERC20Like(token).balanceOf(destinationAddress), amount); - } - - function testTransferTrancheTokensFromCentrifugeWithoutMemberFails( - uint64 poolId, - bytes16 trancheId, - string memory tokenName, - string memory tokenSymbol, - uint8 decimals, - uint128 price, - address destinationAddress, - uint128 amount - ) public { - vm.assume(destinationAddress != address(0)); - connector.addPool(poolId); - connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); - bridgedConnector.deployTranche(poolId, trancheId); - - vm.expectRevert(bytes("CentrifugeConnector/not-a-member")); - connector.incomingTransferTrancheTokens(poolId, trancheId, uint64(block.chainid), destinationAddress, amount); - - (address token,,,,,) = bridgedConnector.tranches(poolId, trancheId); - assertEq(ERC20Like(token).balanceOf(destinationAddress), 0); - } - - function testTransferTrancheTokensToEVM( - uint64 poolId, - bytes16 trancheId, - string memory tokenName, - string memory tokenSymbol, - uint8 decimals, - uint128 price, - uint64 validUntil, - address destinationAddress, - uint128 amount - ) public { - vm.assume(validUntil > block.timestamp + 7 days); - vm.assume(destinationAddress != address(0)); - vm.assume(amount > 0); - connector.addPool(poolId); - connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); - bridgedConnector.deployTranche(poolId, trancheId); - connector.updateMember(poolId, trancheId, destinationAddress, validUntil); - connector.updateMember(poolId, trancheId, address(this), validUntil); - - // Fund this address with amount - connector.incomingTransferTrancheTokens(poolId, trancheId, uint64(block.chainid), address(this), amount); - (address token,,,,,) = bridgedConnector.tranches(poolId, trancheId); - assertEq(ERC20Like(token).balanceOf(address(this)), amount); - - // Approve and transfer amount from this address to destinationAddress - ERC20Like(token).approve(address(bridgedConnector), amount); - bridgedConnector.transferTrancheTokensToEVM( - poolId, trancheId, uint64(block.chainid), destinationAddress, amount - ); - assertEq(ERC20Like(token).balanceOf(address(this)), 0); - } - - function testIncreaseInvestOrder( - uint64 poolId, - bytes16 trancheId, - string memory trancheTokenName, - string memory trancheTokenSymbol, - uint8 trancheDecimals, - uint128 price, - uint64 validUntil, - uint128 currency, - uint8 erc20Decimals, - uint128 amount - ) public { - vm.assume(amount > 0); - vm.assume(trancheDecimals & erc20Decimals > 0); - vm.assume(validUntil > block.timestamp + 7 days); - vm.assume(currency != 0); - - ERC20 erc20 = newErc20("X's Dollar", "USDX", erc20Decimals); - - vm.expectRevert(bytes("CentrifugeConnector/unknown-tranche-token")); - bridgedConnector.increaseInvestOrder(poolId, trancheId, address(erc20), amount); - connector.addPool(poolId); - connector.addTranche(poolId, trancheId, trancheTokenName, trancheTokenSymbol, trancheDecimals, price); - bridgedConnector.deployTranche(poolId, trancheId); - - vm.expectRevert(bytes("CentrifugeConnector/not-a-member")); - bridgedConnector.increaseInvestOrder(poolId, trancheId, address(erc20), amount); - connector.updateMember(poolId, trancheId, address(this), validUntil); - - vm.expectRevert(bytes("CentrifugeConnector/unknown-currency")); - bridgedConnector.increaseInvestOrder(poolId, trancheId, address(erc20), amount); - connector.addCurrency(currency, address(erc20)); - - vm.expectRevert(bytes("CentrifugeConnector/pool-currency-not-allowed")); - bridgedConnector.increaseInvestOrder(poolId, trancheId, address(erc20), amount); - connector.allowPoolCurrency(poolId, currency); - - erc20.approve(address(bridgedConnector), type(uint256).max); - erc20.mint(address(this), amount); - assertEq(erc20.balanceOf(address(bridgedConnector.escrow())), 0); - bridgedConnector.increaseInvestOrder(poolId, trancheId, address(erc20), amount); - assertEq(erc20.balanceOf(address(bridgedConnector.escrow())), amount); - assertEq(erc20.balanceOf(address(this)), 0); - } - - function testDecreaseInvestOrder( - uint64 poolId, - bytes16 trancheId, - string memory trancheTokenName, - string memory trancheTokenSymbol, - uint8 trancheDecimals, - uint128 price, - uint64 validUntil, - uint128 currency, - uint8 erc20Decimals, - uint128 amount - ) public { - vm.assume(amount > 0); - vm.assume(trancheDecimals & erc20Decimals > 0); - vm.assume(validUntil > block.timestamp + 7 days); - vm.assume(currency != 0); - - ERC20 erc20 = newErc20("X's Dollar", "USDX", erc20Decimals); - - vm.expectRevert(bytes("CentrifugeConnector/unknown-tranche-token")); - bridgedConnector.decreaseInvestOrder(poolId, trancheId, address(erc20), amount); - connector.addPool(poolId); - connector.addTranche(poolId, trancheId, trancheTokenName, trancheTokenSymbol, trancheDecimals, price); - bridgedConnector.deployTranche(poolId, trancheId); - - vm.expectRevert(bytes("CentrifugeConnector/not-a-member")); - bridgedConnector.decreaseInvestOrder(poolId, trancheId, address(erc20), amount); - connector.updateMember(poolId, trancheId, address(this), validUntil); - - vm.expectRevert(bytes("CentrifugeConnector/unknown-currency")); - bridgedConnector.decreaseInvestOrder(poolId, trancheId, address(erc20), amount); - connector.addCurrency(currency, address(erc20)); - - vm.expectRevert(bytes("CentrifugeConnector/pool-currency-not-allowed")); - bridgedConnector.decreaseInvestOrder(poolId, trancheId, address(erc20), amount); - connector.allowPoolCurrency(poolId, currency); - - assertEq(erc20.balanceOf(address(bridgedConnector.escrow())), 0); - assertEq(erc20.balanceOf(address(this)), 0); - bridgedConnector.decreaseInvestOrder(poolId, trancheId, address(erc20), amount); - assertEq(erc20.balanceOf(address(bridgedConnector.escrow())), 0); - assertEq(erc20.balanceOf(address(this)), 0); - } - - function testIncreaseRedeemOrder( - uint64 poolId, - bytes16 trancheId, - string memory trancheTokenName, - string memory trancheTokenSymbol, - uint8 trancheDecimals, - uint128 price, - uint64 validUntil, - uint128 currency, - uint8 erc20Decimals, - uint128 amount - ) public { - vm.assume(amount > 0); - vm.assume(trancheDecimals & erc20Decimals > 0); - vm.assume(validUntil > block.timestamp + 7 days); - vm.assume(currency != 0); - - ERC20 erc20 = newErc20("X's Dollar", "USDX", erc20Decimals); - - vm.expectRevert(bytes("CentrifugeConnector/unknown-tranche-token")); - bridgedConnector.increaseRedeemOrder(poolId, trancheId, address(erc20), amount); - connector.addPool(poolId); - connector.addTranche(poolId, trancheId, trancheTokenName, trancheTokenSymbol, trancheDecimals, price); - bridgedConnector.deployTranche(poolId, trancheId); - - vm.expectRevert(bytes("CentrifugeConnector/not-a-member")); - bridgedConnector.increaseRedeemOrder(poolId, trancheId, address(erc20), amount); - connector.updateMember(poolId, trancheId, address(this), validUntil); - - vm.expectRevert(bytes("CentrifugeConnector/unknown-currency")); - bridgedConnector.increaseRedeemOrder(poolId, trancheId, address(erc20), amount); - connector.addCurrency(currency, address(erc20)); - - vm.expectRevert(bytes("CentrifugeConnector/pool-currency-not-allowed")); - bridgedConnector.increaseRedeemOrder(poolId, trancheId, address(erc20), amount); - connector.allowPoolCurrency(poolId, currency); - - assertEq(erc20.balanceOf(address(bridgedConnector.escrow())), 0); - assertEq(erc20.balanceOf(address(this)), 0); - bridgedConnector.increaseRedeemOrder(poolId, trancheId, address(erc20), amount); - assertEq(erc20.balanceOf(address(bridgedConnector.escrow())), 0); - assertEq(erc20.balanceOf(address(this)), 0); - } - - function testDecreaseRedeemOrder( - uint64 poolId, - bytes16 trancheId, - string memory trancheTokenName, - string memory trancheTokenSymbol, - uint8 trancheDecimals, - uint128 price, - uint64 validUntil, - uint128 currency, - uint8 erc20Decimals, - uint128 amount - ) public { - vm.assume(amount > 0); - vm.assume(trancheDecimals & erc20Decimals > 0); - vm.assume(validUntil > block.timestamp + 7 days); - vm.assume(currency != 0); - - ERC20 erc20 = newErc20("X's Dollar", "USDX", erc20Decimals); - - vm.expectRevert(bytes("CentrifugeConnector/unknown-tranche-token")); - bridgedConnector.decreaseRedeemOrder(poolId, trancheId, address(erc20), amount); - connector.addPool(poolId); - connector.addTranche(poolId, trancheId, trancheTokenName, trancheTokenSymbol, trancheDecimals, price); - bridgedConnector.deployTranche(poolId, trancheId); - - vm.expectRevert(bytes("CentrifugeConnector/not-a-member")); - bridgedConnector.decreaseRedeemOrder(poolId, trancheId, address(erc20), amount); - connector.updateMember(poolId, trancheId, address(this), validUntil); - - vm.expectRevert(bytes("CentrifugeConnector/unknown-currency")); - bridgedConnector.decreaseRedeemOrder(poolId, trancheId, address(erc20), amount); - connector.addCurrency(currency, address(erc20)); - - vm.expectRevert(bytes("CentrifugeConnector/pool-currency-not-allowed")); - bridgedConnector.decreaseRedeemOrder(poolId, trancheId, address(erc20), amount); - connector.allowPoolCurrency(poolId, currency); - - assertEq(erc20.balanceOf(address(bridgedConnector.escrow())), 0); - assertEq(erc20.balanceOf(address(this)), 0); - bridgedConnector.decreaseRedeemOrder(poolId, trancheId, address(erc20), amount); - assertEq(erc20.balanceOf(address(bridgedConnector.escrow())), 0); - assertEq(erc20.balanceOf(address(this)), 0); - } - - function testCollectRedeem( - uint64 poolId, - bytes16 trancheId, - string memory trancheTokenName, - string memory trancheTokenSymbol, - uint8 trancheDecimals, - uint128 price, - uint64 validUntil, - uint128 amount - ) public { - vm.assume(amount > 0); - vm.assume(trancheDecimals > 0); - vm.assume(validUntil > block.timestamp + 7 days); - - vm.expectRevert(bytes("CentrifugeConnector/unknown-tranche-token")); - bridgedConnector.collectRedeem(poolId, trancheId); - connector.addPool(poolId); - connector.addTranche(poolId, trancheId, trancheTokenName, trancheTokenSymbol, trancheDecimals, price); - bridgedConnector.deployTranche(poolId, trancheId); - - vm.expectRevert(bytes("CentrifugeConnector/not-a-member")); - bridgedConnector.collectRedeem(poolId, trancheId); - connector.updateMember(poolId, trancheId, address(this), validUntil); - - bridgedConnector.collectRedeem(poolId, trancheId); - } - - function testCollectInvest( - uint64 poolId, - bytes16 trancheId, - string memory trancheTokenName, - string memory trancheTokenSymbol, - uint8 trancheDecimals, - uint128 price, - uint64 validUntil, - uint128 amount - ) public { - vm.assume(amount > 0); - vm.assume(trancheDecimals > 0); - vm.assume(validUntil > block.timestamp + 7 days); - - vm.expectRevert(bytes("CentrifugeConnector/unknown-tranche-token")); - bridgedConnector.collectInvest(poolId, trancheId); - connector.addPool(poolId); - connector.addTranche(poolId, trancheId, trancheTokenName, trancheTokenSymbol, trancheDecimals, price); - bridgedConnector.deployTranche(poolId, trancheId); - - vm.expectRevert(bytes("CentrifugeConnector/not-a-member")); - bridgedConnector.collectInvest(poolId, trancheId); - connector.updateMember(poolId, trancheId, address(this), validUntil); - - bridgedConnector.collectInvest(poolId, trancheId); - } + // function testAddCurrencyWorks(uint128 currency, uint128 badCurrency) public { + // vm.assume(currency > 0); + // vm.assume(badCurrency > 0); + // vm.assume(currency != badCurrency); + + // ERC20 erc20 = newErc20("X's Dollar", "USDX", 42); + // connector.addCurrency(currency, address(erc20)); + // (address address_) = bridgedConnector.currencyIdToAddress(currency); + // assertEq(address_, address(erc20)); + + // // Verify we can't override the same currency id another address + // ERC20 badErc20 = newErc20("BadActor's Dollar", "BADUSD", 66); + // vm.expectRevert(bytes("CentrifugeConnector/currency-id-in-use")); + // connector.addCurrency(currency, address(badErc20)); + // assertEq(bridgedConnector.currencyIdToAddress(currency), address(erc20)); + + // // Verify we can't add a currency address that already exists associated with a different currency id + // vm.expectRevert(bytes("CentrifugeConnector/currency-address-in-use")); + // connector.addCurrency(badCurrency, address(erc20)); + // assertEq(bridgedConnector.currencyIdToAddress(currency), address(erc20)); + // } + + // function testAddPoolWorks(uint64 poolId) public { + // connector.addPool(poolId); + // (uint64 actualPoolId,) = bridgedConnector.pools(poolId); + // assertEq(uint256(actualPoolId), uint256(poolId)); + // } + + // function testAllowPoolCurrencyWorks(uint128 currency, uint64 poolId) public { + // ERC20 token = newErc20("X's Dollar", "USDX", 42); + // connector.addCurrency(currency, address(token)); + // connector.addPool(poolId); + + // connector.allowPoolCurrency(poolId, currency); + // assertTrue(bridgedConnector.allowedPoolCurrencies(poolId, address(token))); + // } + + // function testAllowPoolCurrencyWithUnknownCurrencyFails(uint128 currency, uint64 poolId) public { + // connector.addPool(poolId); + // vm.expectRevert(bytes("CentrifugeConnector/unknown-currency")); + // connector.allowPoolCurrency(poolId, currency); + // } + + // function testAddingPoolMultipleTimesFails(uint64 poolId) public { + // connector.addPool(poolId); + + // vm.expectRevert(bytes("CentrifugeConnector/pool-already-added")); + // connector.addPool(poolId); + // } + + // function testAddingPoolAsNonRouterFails(uint64 poolId) public { + // vm.expectRevert(bytes("CentrifugeConnector/not-the-gateway")); + // bridgedConnector.addPool(poolId); + // } + + // function testAddingSingleTrancheWorks( + // uint64 poolId, + // bytes16 trancheId, + // string memory tokenName, + // string memory tokenSymbol, + // uint8 decimals, + // uint128 price + // ) public { + // connector.addPool(poolId); + // (uint64 actualPoolId,) = bridgedConnector.pools(poolId); + // assertEq(uint256(actualPoolId), uint256(poolId)); + + // connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); + // bridgedConnector.deployTranche(poolId, trancheId); + + // ( + // address token_, + // uint256 latestPrice, + // , + // string memory actualTokenName, + // string memory actualTokenSymbol, + // uint8 actualDecimals + // ) = bridgedConnector.tranches(poolId, trancheId); + // assertTrue(token_ != address(0)); + // assertEq(latestPrice, price); + + // // Comparing raw input to output can erroneously fail when a byte string is given. + // // Intended behaviour is that byte strings will be treated as bytes and converted to strings + // // instead of treated as strings themselves. This conversion from string to bytes32 to string + // // is used to simulate this intended behaviour. + // assertEq(actualTokenName, bytes32ToString(stringToBytes32(tokenName))); + // assertEq(actualTokenSymbol, bytes32ToString(stringToBytes32(tokenSymbol))); + // assertEq(actualDecimals, decimals); + + // RestrictedTokenLike token = RestrictedTokenLike(token_); + // assertEq(token.name(), bytes32ToString(stringToBytes32(tokenName))); + // assertEq(token.symbol(), bytes32ToString(stringToBytes32(tokenSymbol))); + // assertEq(token.decimals(), decimals); + // } + + // function testAddingTrancheMultipleTimesFails( + // uint64 poolId, + // uint8 decimals, + // string memory tokenName, + // string memory tokenSymbol, + // bytes16 trancheId, + // uint128 price + // ) public { + // connector.addPool(poolId); + // connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); + + // vm.expectRevert(bytes("CentrifugeConnector/tranche-already-added")); + // connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); + // } + + // function testAddingMultipleTranchesWorks( + // uint64 poolId, + // bytes16[] calldata trancheIds, + // string memory tokenName, + // string memory tokenSymbol, + // uint8 decimals, + // uint128 price + // ) public { + // vm.assume(trancheIds.length > 0 && trancheIds.length < 5); + // vm.assume(!hasDuplicates(trancheIds)); + // connector.addPool(poolId); + + // for (uint256 i = 0; i < trancheIds.length; i++) { + // connector.addTranche(poolId, trancheIds[i], tokenName, tokenSymbol, decimals, price); + // bridgedConnector.deployTranche(poolId, trancheIds[i]); + // (address token, uint256 latestPrice,,,, uint8 actualDecimals) = + // bridgedConnector.tranches(poolId, trancheIds[i]); + // assertEq(latestPrice, price); + // assertTrue(token != address(0)); + // assertEq(actualDecimals, decimals); + // } + // } + + // function testAddingTranchesAsNonRouterFails( + // uint64 poolId, + // bytes16 trancheId, + // string memory tokenName, + // string memory tokenSymbol, + // uint8 decimals, + // uint128 price + // ) public { + // connector.addPool(poolId); + // vm.expectRevert(bytes("CentrifugeConnector/not-the-gateway")); + // bridgedConnector.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); + // } + + // function testAddingTranchesForNonExistentPoolFails( + // uint64 poolId, + // bytes16 trancheId, + // string memory tokenName, + // string memory tokenSymbol, + // uint8 decimals, + // uint128 price + // ) public { + // vm.expectRevert(bytes("CentrifugeConnector/invalid-pool")); + // connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); + // } + + // function testDeployingTrancheMultipleTimesFails( + // uint64 poolId, + // uint8 decimals, + // string memory tokenName, + // string memory tokenSymbol, + // bytes16 trancheId, + // uint128 price + // ) public { + // connector.addPool(poolId); + // connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); + // bridgedConnector.deployTranche(poolId, trancheId); + + // vm.expectRevert(bytes("CentrifugeConnector/tranche-already-deployed")); + // bridgedConnector.deployTranche(poolId, trancheId); + // } + + // function testDeployingWrongTrancheFails( + // uint64 poolId, + // uint8 decimals, + // string memory tokenName, + // string memory tokenSymbol, + // bytes16 trancheId, + // bytes16 wrongTrancheId, + // uint128 price + // ) public { + // vm.assume(trancheId != wrongTrancheId); + + // connector.addPool(poolId); + // (uint64 actualPoolId,) = bridgedConnector.pools(poolId); + // assertEq(uint256(actualPoolId), uint256(poolId)); + + // connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); + // vm.expectRevert(bytes("CentrifugeConnector/invalid-pool-or-tranche")); + // bridgedConnector.deployTranche(poolId, wrongTrancheId); + // } + + // function testDeployingTrancheOnNonExistentPoolFails( + // uint64 poolId, + // uint8 decimals, + // uint64 wrongPoolId, + // string memory tokenName, + // string memory tokenSymbol, + // bytes16 trancheId, + // uint128 price + // ) public { + // vm.assume(poolId != wrongPoolId); + + // connector.addPool(poolId); + // (uint64 actualPoolId,) = bridgedConnector.pools(poolId); + // assertEq(uint256(actualPoolId), uint256(poolId)); + + // connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); + // vm.expectRevert(bytes("CentrifugeConnector/invalid-pool-or-tranche")); + // bridgedConnector.deployTranche(wrongPoolId, trancheId); + // } + + // function testUpdatingMemberWorks(uint64 poolId, uint8 decimals, bytes16 trancheId, address user, uint64 validUntil) + // public + // { + // vm.assume(validUntil >= block.timestamp); + // vm.assume(user != address(0)); + + // connector.addPool(poolId); + // connector.addTranche(poolId, trancheId, "Some Name", "SYMBOL", decimals, 123); + // bridgedConnector.deployTranche(poolId, trancheId); + // connector.updateMember(poolId, trancheId, user, validUntil); + + // (address token_,,,,,) = bridgedConnector.tranches(poolId, trancheId); + // RestrictedTokenLike token = RestrictedTokenLike(token_); + // assertTrue(token.hasMember(user)); + + // MemberlistLike memberlist = MemberlistLike(token.memberlist()); + // assertEq(memberlist.members(user), validUntil); + // } + + // function testUpdatingMemberAsNonRouterFails(uint64 poolId, bytes16 trancheId, address user, uint64 validUntil) + // public + // { + // vm.assume(validUntil <= block.timestamp); + // vm.assume(user != address(0)); + + // vm.expectRevert(bytes("CentrifugeConnector/not-the-gateway")); + // bridgedConnector.updateMember(poolId, trancheId, user, validUntil); + // } + + // function testUpdatingMemberForNonExistentPoolFails( + // uint64 poolId, + // bytes16 trancheId, + // address user, + // uint64 validUntil + // ) public { + // vm.assume(validUntil > block.timestamp); + // bridgedConnector.file("gateway", address(this)); + // vm.expectRevert(bytes("CentrifugeConnector/invalid-pool-or-tranche")); + // bridgedConnector.updateMember(poolId, trancheId, user, validUntil); + // } + + // function testUpdatingMemberForNonExistentTrancheFails( + // uint64 poolId, + // bytes16 trancheId, + // address user, + // uint64 validUntil + // ) public { + // vm.assume(validUntil > block.timestamp); + // connector.addPool(poolId); + + // vm.expectRevert(bytes("CentrifugeConnector/invalid-pool-or-tranche")); + // connector.updateMember(poolId, trancheId, user, validUntil); + // } + + // function testUpdatingTokenPriceWorks(uint64 poolId, uint8 decimals, bytes16 trancheId, uint128 price) public { + // connector.addPool(poolId); + // connector.addTranche(poolId, trancheId, "Some Name", "SYMBOL", decimals, 123); + // connector.updateTokenPrice(poolId, trancheId, price); + + // (, uint256 latestPrice, uint256 lastPriceUpdate,,,) = bridgedConnector.tranches(poolId, trancheId); + // assertEq(latestPrice, price); + // assertEq(lastPriceUpdate, block.timestamp); + // } + + // function testUpdatingTokenPriceAsNonRouterFails(uint64 poolId, uint8 decimals, bytes16 trancheId, uint128 price) + // public + // { + // connector.addPool(poolId); + // connector.addTranche(poolId, trancheId, "Some Name", "SYMBOL", decimals, 123); + // vm.expectRevert(bytes("CentrifugeConnector/not-the-gateway")); + // bridgedConnector.updateTokenPrice(poolId, trancheId, price); + // } + + // function testUpdatingTokenPriceForNonExistentPoolFails(uint64 poolId, bytes16 trancheId, uint128 price) public { + // bridgedConnector.file("gateway", address(this)); + // vm.expectRevert(bytes("CentrifugeConnector/invalid-pool-or-tranche")); + // bridgedConnector.updateTokenPrice(poolId, trancheId, price); + // } + + // function testUpdatingTokenPriceForNonExistentTrancheFails(uint64 poolId, bytes16 trancheId, uint128 price) public { + // connector.addPool(poolId); + + // vm.expectRevert(bytes("CentrifugeConnector/invalid-pool-or-tranche")); + // connector.updateTokenPrice(poolId, trancheId, price); + // } + + // function testIncomingTransferWithoutEscrowFundsFails( + // string memory tokenName, + // string memory tokenSymbol, + // uint8 decimals, + // uint128 currency, + // bytes32 sender, + // address recipient, + // uint128 amount + // ) public { + // vm.assume(decimals > 0); + // vm.assume(amount > 0); + // vm.assume(recipient != address(0)); + + // ERC20 erc20 = newErc20(tokenName, tokenSymbol, decimals); + // connector.addCurrency(currency, address(erc20)); + + // assertEq(erc20.balanceOf(address(bridgedConnector.escrow())), 0); + // vm.expectRevert(bytes("ERC20/insufficient-balance")); + // connector.incomingTransfer(currency, sender, bytes32(bytes20(recipient)), amount); + // assertEq(erc20.balanceOf(address(bridgedConnector.escrow())), 0); + // assertEq(erc20.balanceOf(recipient), 0); + // } + + // function testIncomingTransferWorks( + // string memory tokenName, + // string memory tokenSymbol, + // uint8 decimals, + // uint128 currency, + // bytes32 sender, + // address recipient, + // uint128 amount + // ) public { + // vm.assume(decimals > 0); + // vm.assume(amount > 0); + // vm.assume(currency != 0); + // vm.assume(recipient != address(0)); + + // ERC20 erc20 = newErc20(tokenName, tokenSymbol, decimals); + // connector.addCurrency(currency, address(erc20)); + + // // First, an outgoing transfer must take place which has funds currency of the currency moved to + // // the escrow account, from which funds are moved from into the recipient on an incoming transfer. + // erc20.approve(address(bridgedConnector), type(uint256).max); + // erc20.mint(address(this), amount); + // bridgedConnector.transfer(address(erc20), bytes32(bytes20(recipient)), amount); + // assertEq(erc20.balanceOf(address(bridgedConnector.escrow())), amount); + + // // Now we test the incoming message + // connector.incomingTransfer(currency, sender, bytes32(bytes20(recipient)), amount); + // assertEq(erc20.balanceOf(address(bridgedConnector.escrow())), 0); + // assertEq(erc20.balanceOf(recipient), amount); + // } + + // // Verify that funds are moved from the msg.sender into the escrow account + // function testOutgoingTransferWorks( + // string memory tokenName, + // string memory tokenSymbol, + // uint8 decimals, + // uint128 initialBalance, + // uint128 currency, + // bytes32 recipient, + // uint128 amount + // ) public { + // vm.assume(decimals > 0); + // vm.assume(amount > 0); + // vm.assume(currency != 0); + // vm.assume(initialBalance >= amount); + + // ERC20 erc20 = newErc20(tokenName, tokenSymbol, decimals); + + // vm.expectRevert(bytes("CentrifugeConnector/unknown-currency")); + // bridgedConnector.transfer(address(erc20), recipient, amount); + // connector.addCurrency(currency, address(erc20)); + + // erc20.mint(address(this), initialBalance); + // assertEq(erc20.balanceOf(address(this)), initialBalance); + // assertEq(erc20.balanceOf(address(bridgedConnector.escrow())), 0); + // erc20.approve(address(bridgedConnector), type(uint256).max); + + // bridgedConnector.transfer(address(erc20), recipient, amount); + // assertEq(erc20.balanceOf(address(this)), initialBalance - amount); + // assertEq(erc20.balanceOf(address(bridgedConnector.escrow())), amount); + // } + // // Test transferring `amount` to the address(this)'s account (Centrifuge Chain -> EVM like) and then try + // // transferring that amount to a `centChainAddress` (EVM -> Centrifuge Chain like). + + // function testTransferTrancheTokensToCentrifuge( + // uint64 poolId, + // uint8 decimals, + // string memory tokenName, + // string memory tokenSymbol, + // bytes16 trancheId, + // uint128 price, + // bytes32 centChainAddress, + // uint128 amount, + // uint64 validUntil + // ) public { + // vm.assume(validUntil > block.timestamp + 7 days); + // connector.addPool(poolId); + // connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); + // bridgedConnector.deployTranche(poolId, trancheId); + // connector.updateMember(poolId, trancheId, address(this), validUntil); + + // // fund this account with amount + // connector.incomingTransferTrancheTokens(poolId, trancheId, uint64(block.chainid), address(this), amount); + + // // Verify the address(this) has the expected amount + // (address tokenAddress,,,,,) = bridgedConnector.tranches(poolId, trancheId); + // RestrictedTokenLike token = RestrictedTokenLike(tokenAddress); + // assertEq(token.balanceOf(address(this)), amount); + + // // Now send the transfer from EVM -> Cent Chain + // token.approve(address(bridgedConnector), amount); + // bridgedConnector.transferTrancheTokensToCentrifuge(poolId, trancheId, centChainAddress, amount); + // assertEq(token.balanceOf(address(this)), 0); + + // // Finally, verify the connector called `router.send` + // bytes memory message = ConnectorMessages.formatTransferTrancheTokens( + // poolId, + // trancheId, + // bytes32(bytes20(address(this))), + // ConnectorMessages.formatDomain(ConnectorMessages.Domain.Centrifuge), + // centChainAddress, + // amount + // ); + // assertEq(mockXcmRouter.sentMessages(message), true); + // } + + // function testTransferTrancheTokensFromCentrifuge( + // uint64 poolId, + // bytes16 trancheId, + // string memory tokenName, + // string memory tokenSymbol, + // uint8 decimals, + // uint128 price, + // uint64 validUntil, + // address destinationAddress, + // uint128 amount + // ) public { + // vm.assume(validUntil > block.timestamp + 7 days); + // vm.assume(destinationAddress != address(0)); + + // connector.addPool(poolId); + // connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); + // bridgedConnector.deployTranche(poolId, trancheId); + // connector.updateMember(poolId, trancheId, destinationAddress, validUntil); + + // connector.incomingTransferTrancheTokens(poolId, trancheId, uint64(block.chainid), destinationAddress, amount); + // (address token,,,,,) = bridgedConnector.tranches(poolId, trancheId); + // assertEq(ERC20Like(token).balanceOf(destinationAddress), amount); + // } + + // function testTransferTrancheTokensFromCentrifugeWithoutMemberFails( + // uint64 poolId, + // bytes16 trancheId, + // string memory tokenName, + // string memory tokenSymbol, + // uint8 decimals, + // uint128 price, + // address destinationAddress, + // uint128 amount + // ) public { + // vm.assume(destinationAddress != address(0)); + // connector.addPool(poolId); + // connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); + // bridgedConnector.deployTranche(poolId, trancheId); + + // vm.expectRevert(bytes("CentrifugeConnector/not-a-member")); + // connector.incomingTransferTrancheTokens(poolId, trancheId, uint64(block.chainid), destinationAddress, amount); + + // (address token,,,,,) = bridgedConnector.tranches(poolId, trancheId); + // assertEq(ERC20Like(token).balanceOf(destinationAddress), 0); + // } + + // function testTransferTrancheTokensToEVM( + // uint64 poolId, + // bytes16 trancheId, + // string memory tokenName, + // string memory tokenSymbol, + // uint8 decimals, + // uint128 price, + // uint64 validUntil, + // address destinationAddress, + // uint128 amount + // ) public { + // vm.assume(validUntil > block.timestamp + 7 days); + // vm.assume(destinationAddress != address(0)); + // vm.assume(amount > 0); + // connector.addPool(poolId); + // connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); + // bridgedConnector.deployTranche(poolId, trancheId); + // connector.updateMember(poolId, trancheId, destinationAddress, validUntil); + // connector.updateMember(poolId, trancheId, address(this), validUntil); + + // // Fund this address with amount + // connector.incomingTransferTrancheTokens(poolId, trancheId, uint64(block.chainid), address(this), amount); + // (address token,,,,,) = bridgedConnector.tranches(poolId, trancheId); + // assertEq(ERC20Like(token).balanceOf(address(this)), amount); + + // // Approve and transfer amount from this address to destinationAddress + // ERC20Like(token).approve(address(bridgedConnector), amount); + // bridgedConnector.transferTrancheTokensToEVM( + // poolId, trancheId, uint64(block.chainid), destinationAddress, amount + // ); + // assertEq(ERC20Like(token).balanceOf(address(this)), 0); + // } + + // function testIncreaseInvestOrder( + // uint64 poolId, + // bytes16 trancheId, + // string memory trancheTokenName, + // string memory trancheTokenSymbol, + // uint8 trancheDecimals, + // uint128 price, + // uint64 validUntil, + // uint128 currency, + // uint8 erc20Decimals, + // uint128 amount + // ) public { + // vm.assume(amount > 0); + // vm.assume(trancheDecimals & erc20Decimals > 0); + // vm.assume(validUntil > block.timestamp + 7 days); + // vm.assume(currency != 0); + + // ERC20 erc20 = newErc20("X's Dollar", "USDX", erc20Decimals); + + // vm.expectRevert(bytes("CentrifugeConnector/unknown-tranche-token")); + // bridgedConnector.increaseInvestOrder(poolId, trancheId, address(erc20), amount); + // connector.addPool(poolId); + // connector.addTranche(poolId, trancheId, trancheTokenName, trancheTokenSymbol, trancheDecimals, price); + // bridgedConnector.deployTranche(poolId, trancheId); + + // vm.expectRevert(bytes("CentrifugeConnector/not-a-member")); + // bridgedConnector.increaseInvestOrder(poolId, trancheId, address(erc20), amount); + // connector.updateMember(poolId, trancheId, address(this), validUntil); + + // vm.expectRevert(bytes("CentrifugeConnector/unknown-currency")); + // bridgedConnector.increaseInvestOrder(poolId, trancheId, address(erc20), amount); + // connector.addCurrency(currency, address(erc20)); + + // vm.expectRevert(bytes("CentrifugeConnector/pool-currency-not-allowed")); + // bridgedConnector.increaseInvestOrder(poolId, trancheId, address(erc20), amount); + // connector.allowPoolCurrency(poolId, currency); + + // erc20.approve(address(bridgedConnector), type(uint256).max); + // erc20.mint(address(this), amount); + // assertEq(erc20.balanceOf(address(bridgedConnector.escrow())), 0); + // bridgedConnector.increaseInvestOrder(poolId, trancheId, address(erc20), amount); + // assertEq(erc20.balanceOf(address(bridgedConnector.escrow())), amount); + // assertEq(erc20.balanceOf(address(this)), 0); + // } + + // function testDecreaseInvestOrder( + // uint64 poolId, + // bytes16 trancheId, + // string memory trancheTokenName, + // string memory trancheTokenSymbol, + // uint8 trancheDecimals, + // uint128 price, + // uint64 validUntil, + // uint128 currency, + // uint8 erc20Decimals, + // uint128 amount + // ) public { + // vm.assume(amount > 0); + // vm.assume(trancheDecimals & erc20Decimals > 0); + // vm.assume(validUntil > block.timestamp + 7 days); + // vm.assume(currency != 0); + + // ERC20 erc20 = newErc20("X's Dollar", "USDX", erc20Decimals); + + // vm.expectRevert(bytes("CentrifugeConnector/unknown-tranche-token")); + // bridgedConnector.decreaseInvestOrder(poolId, trancheId, address(erc20), amount); + // connector.addPool(poolId); + // connector.addTranche(poolId, trancheId, trancheTokenName, trancheTokenSymbol, trancheDecimals, price); + // bridgedConnector.deployTranche(poolId, trancheId); + + // vm.expectRevert(bytes("CentrifugeConnector/not-a-member")); + // bridgedConnector.decreaseInvestOrder(poolId, trancheId, address(erc20), amount); + // connector.updateMember(poolId, trancheId, address(this), validUntil); + + // vm.expectRevert(bytes("CentrifugeConnector/unknown-currency")); + // bridgedConnector.decreaseInvestOrder(poolId, trancheId, address(erc20), amount); + // connector.addCurrency(currency, address(erc20)); + + // vm.expectRevert(bytes("CentrifugeConnector/pool-currency-not-allowed")); + // bridgedConnector.decreaseInvestOrder(poolId, trancheId, address(erc20), amount); + // connector.allowPoolCurrency(poolId, currency); + + // assertEq(erc20.balanceOf(address(bridgedConnector.escrow())), 0); + // assertEq(erc20.balanceOf(address(this)), 0); + // bridgedConnector.decreaseInvestOrder(poolId, trancheId, address(erc20), amount); + // assertEq(erc20.balanceOf(address(bridgedConnector.escrow())), 0); + // assertEq(erc20.balanceOf(address(this)), 0); + // } + + // function testIncreaseRedeemOrder( + // uint64 poolId, + // bytes16 trancheId, + // string memory trancheTokenName, + // string memory trancheTokenSymbol, + // uint8 trancheDecimals, + // uint128 price, + // uint64 validUntil, + // uint128 currency, + // uint8 erc20Decimals, + // uint128 amount + // ) public { + // vm.assume(amount > 0); + // vm.assume(trancheDecimals & erc20Decimals > 0); + // vm.assume(validUntil > block.timestamp + 7 days); + // vm.assume(currency != 0); + + // ERC20 erc20 = newErc20("X's Dollar", "USDX", erc20Decimals); + + // vm.expectRevert(bytes("CentrifugeConnector/unknown-tranche-token")); + // bridgedConnector.increaseRedeemOrder(poolId, trancheId, address(erc20), amount); + // connector.addPool(poolId); + // connector.addTranche(poolId, trancheId, trancheTokenName, trancheTokenSymbol, trancheDecimals, price); + // bridgedConnector.deployTranche(poolId, trancheId); + + // vm.expectRevert(bytes("CentrifugeConnector/not-a-member")); + // bridgedConnector.increaseRedeemOrder(poolId, trancheId, address(erc20), amount); + // connector.updateMember(poolId, trancheId, address(this), validUntil); + + // vm.expectRevert(bytes("CentrifugeConnector/unknown-currency")); + // bridgedConnector.increaseRedeemOrder(poolId, trancheId, address(erc20), amount); + // connector.addCurrency(currency, address(erc20)); + + // vm.expectRevert(bytes("CentrifugeConnector/pool-currency-not-allowed")); + // bridgedConnector.increaseRedeemOrder(poolId, trancheId, address(erc20), amount); + // connector.allowPoolCurrency(poolId, currency); + + // assertEq(erc20.balanceOf(address(bridgedConnector.escrow())), 0); + // assertEq(erc20.balanceOf(address(this)), 0); + // bridgedConnector.increaseRedeemOrder(poolId, trancheId, address(erc20), amount); + // assertEq(erc20.balanceOf(address(bridgedConnector.escrow())), 0); + // assertEq(erc20.balanceOf(address(this)), 0); + // } + + // function testDecreaseRedeemOrder( + // uint64 poolId, + // bytes16 trancheId, + // string memory trancheTokenName, + // string memory trancheTokenSymbol, + // uint8 trancheDecimals, + // uint128 price, + // uint64 validUntil, + // uint128 currency, + // uint8 erc20Decimals, + // uint128 amount + // ) public { + // vm.assume(amount > 0); + // vm.assume(trancheDecimals & erc20Decimals > 0); + // vm.assume(validUntil > block.timestamp + 7 days); + // vm.assume(currency != 0); + + // ERC20 erc20 = newErc20("X's Dollar", "USDX", erc20Decimals); + + // vm.expectRevert(bytes("CentrifugeConnector/unknown-tranche-token")); + // bridgedConnector.decreaseRedeemOrder(poolId, trancheId, address(erc20), amount); + // connector.addPool(poolId); + // connector.addTranche(poolId, trancheId, trancheTokenName, trancheTokenSymbol, trancheDecimals, price); + // bridgedConnector.deployTranche(poolId, trancheId); + + // vm.expectRevert(bytes("CentrifugeConnector/not-a-member")); + // bridgedConnector.decreaseRedeemOrder(poolId, trancheId, address(erc20), amount); + // connector.updateMember(poolId, trancheId, address(this), validUntil); + + // vm.expectRevert(bytes("CentrifugeConnector/unknown-currency")); + // bridgedConnector.decreaseRedeemOrder(poolId, trancheId, address(erc20), amount); + // connector.addCurrency(currency, address(erc20)); + + // vm.expectRevert(bytes("CentrifugeConnector/pool-currency-not-allowed")); + // bridgedConnector.decreaseRedeemOrder(poolId, trancheId, address(erc20), amount); + // connector.allowPoolCurrency(poolId, currency); + + // assertEq(erc20.balanceOf(address(bridgedConnector.escrow())), 0); + // assertEq(erc20.balanceOf(address(this)), 0); + // bridgedConnector.decreaseRedeemOrder(poolId, trancheId, address(erc20), amount); + // assertEq(erc20.balanceOf(address(bridgedConnector.escrow())), 0); + // assertEq(erc20.balanceOf(address(this)), 0); + // } + + // function testCollectRedeem( + // uint64 poolId, + // bytes16 trancheId, + // string memory trancheTokenName, + // string memory trancheTokenSymbol, + // uint8 trancheDecimals, + // uint128 price, + // uint64 validUntil, + // uint128 amount + // ) public { + // vm.assume(amount > 0); + // vm.assume(trancheDecimals > 0); + // vm.assume(validUntil > block.timestamp + 7 days); + + // vm.expectRevert(bytes("CentrifugeConnector/unknown-tranche-token")); + // bridgedConnector.collectRedeem(poolId, trancheId); + // connector.addPool(poolId); + // connector.addTranche(poolId, trancheId, trancheTokenName, trancheTokenSymbol, trancheDecimals, price); + // bridgedConnector.deployTranche(poolId, trancheId); + + // vm.expectRevert(bytes("CentrifugeConnector/not-a-member")); + // bridgedConnector.collectRedeem(poolId, trancheId); + // connector.updateMember(poolId, trancheId, address(this), validUntil); + + // bridgedConnector.collectRedeem(poolId, trancheId); + // } + + // function testCollectInvest( + // uint64 poolId, + // bytes16 trancheId, + // string memory trancheTokenName, + // string memory trancheTokenSymbol, + // uint8 trancheDecimals, + // uint128 price, + // uint64 validUntil, + // uint128 amount + // ) public { + // vm.assume(amount > 0); + // vm.assume(trancheDecimals > 0); + // vm.assume(validUntil > block.timestamp + 7 days); + + // vm.expectRevert(bytes("CentrifugeConnector/unknown-tranche-token")); + // bridgedConnector.collectInvest(poolId, trancheId); + // connector.addPool(poolId); + // connector.addTranche(poolId, trancheId, trancheTokenName, trancheTokenSymbol, trancheDecimals, price); + // bridgedConnector.deployTranche(poolId, trancheId); + + // vm.expectRevert(bytes("CentrifugeConnector/not-a-member")); + // bridgedConnector.collectInvest(poolId, trancheId); + // connector.updateMember(poolId, trancheId, address(this), validUntil); + + // bridgedConnector.collectInvest(poolId, trancheId); + // } // helpers function newErc20(string memory name, string memory symbol, uint8 decimals) internal returns (ERC20) { From 1c9d1ac9797848ee2110e0f9fc0dcc26fb9ad73a Mon Sep 17 00:00:00 2001 From: ilinzweilin Date: Sun, 25 Jun 2023 22:40:11 +0100 Subject: [PATCH 13/24] add public Liquidity Pool deployment --- src/Connector.sol | 446 +++++++++++------- src/liquidityPool/Factory.sol | 56 +++ .../LiquidityPool.sol} | 50 +- 3 files changed, 344 insertions(+), 208 deletions(-) create mode 100644 src/liquidityPool/Factory.sol rename src/{Tranche4626.sol => liquidityPool/LiquidityPool.sol} (86%) diff --git a/src/Connector.sol b/src/Connector.sol index b1f8fda2..b12e332e 100644 --- a/src/Connector.sol +++ b/src/Connector.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.18; pragma abicoder v2; -import {TrancheTokenFactoryLike, MemberlistFactoryLike} from "./token/factory.sol"; -import {Tranche4626} from "./Tranche4626.sol"; +import { LiquidityPoolFactoryLike, MemberlistFactoryLike } from "./liquidityPool/Factory.sol"; +import { LiquidityPool } from "./liquidityPool/LiquidityPool.sol"; import {ERC20Like} from "./token/restricted.sol"; import {MemberlistLike} from "./token/memberlist.sol"; import "./auth/auth.sol"; @@ -38,13 +38,12 @@ interface GatewayLike { function active() external returns(bool); } -// is RestrictedToken -interface TrancheLike { - // restricted functions +interface LiquidityPoolLike { + // restricted token functions function memberlist() external returns (address); function hasMember(address) external returns (bool); function file(bytes32 what, address data) external; - //erc20 functions + // erc20 functions function mint(address, uint) external; function burn(address, uint) external; function balanceOf(address) external returns (uint); @@ -52,83 +51,96 @@ interface TrancheLike { // 4626 functions function updateTokenPrice(uint128 _tokenPrice) external; function asset() external returns (address); - - } interface EscrowLike { function approve(address token, address spender, uint256 value) external; } +/// @dev storing information about pools on Centrifuge chain struct Pool { uint64 poolId; uint256 createdAt; bool isActive; } +/// @dev storing information about tranches on Centrifuge chain struct Tranche { - address token; - address asset; -} + uint64 poolId; + bytes16 trancheId; + uint256 createdAt; + string tokenName; + string tokenSymbol; + uint8 decimals; + } -struct CentrifugeTranche { +// /// @dev storing information about liquidity pools on EVM chain. One tranche on Centrifuge chain can have multiple corresponding liquidity pools on an EVM chain. +struct LiquidityPoolInfo { uint64 poolId; bytes16 trancheId; } -struct UserTrancheValues { - uint maxDeposit; - uint maxMint; - uint maxWithdraw; - uint maxRedeem; - uint openRedeem; - uint openInvest; +/// @dev storing liquidity pool orders and deposit/redemption limits for a user +struct LPValues { + uint128 maxDeposit; + uint128 maxMint; + uint128 maxWithdraw; + uint128 maxRedeem; + uint128 openRedeem; + uint128 openInvest; } contract CentrifugeConnector is Auth { - mapping(uint64 => Pool) public pools; - mapping(uint64 => mapping(bytes16 => address)) public tranches; - mapping(address => CentrifugeTranche) public centrifugeTranches; - mapping(address => mapping(address => UserTrancheValues)) public orderbook; // contains outstanding orders and limits for each user and tranche + mapping(uint64 => Pool) public pools; // pools on Centrifuge chain - mapping(uint128 => address) public currencyIdToAddress; - // The reverse mapping of `currencyIdToAddress` - mapping(address => uint128) public currencyAddressToId; + mapping(uint64 => mapping(bytes16 => mapping(address => address))) public liquidityPools; // Centrifuge chain tranche -> currency -> liquidity pool address // Todo: add currency + mapping(uint64 => mapping(bytes16 => Tranche)) public tranches; // Centrifuge chain tranches + mapping(address => LiquidityPoolInfo) public addressToLiquidityPoolInfo; + + // mapping(address => Tranche) public tranches; // liquidityPool -> Centrifuge chain tranches + mapping(address => mapping(address => LPValues)) public orderbook; // outstanding liquidity pool orders & limits per user & liquidity pool + mapping(uint128 => address) public currencyIdToAddress; + mapping(address => uint128) public currencyAddressToId; // The reverse mapping of `currencyIdToAddress` mapping(uint64 => mapping(address => bool)) public allowedPoolCurrencies; GatewayLike public gateway; EscrowLike public immutable escrow; - TrancheTokenFactoryLike public immutable tokenFactory; + LiquidityPoolFactoryLike public immutable liquidityPoolFactory; //TODO use for LPPool deployment MemberlistFactoryLike public immutable memberlistFactory; // --- Events --- event File(bytes32 indexed what, address data); event CurrencyAdded(uint128 indexed currency, address indexed currencyAddress); - event PoolAdded(uint256 indexed poolId); - event PoolCurrencyAllowed(uint128 currency, uint64 poolId); - event TrancheAdded(uint256 indexed poolId, bytes16 indexed trancheId); - event TrancheDeployed(uint256 indexed poolId, bytes16 indexed trancheId, address indexed token); - - constructor(address escrow_, address tokenFactory_, address memberlistFactory_) { + 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 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_) { escrow = EscrowLike(escrow_); - tokenFactory = TrancheTokenFactoryLike(tokenFactory_); + liquidityPoolFactory = LiquidityPoolFactoryLike(liquidityPoolFactory_); memberlistFactory = MemberlistFactoryLike(memberlistFactory_); wards[msg.sender] = 1; emit Rely(msg.sender); } - modifier poolActive(address _tranche) { - CentrifugeTranche memory cTranche = centrifugeTranches[_tranche]; - require(pools[cTranche.poolId].isActive, "CentrifugeConnector/pool-deactivated"); + /// @dev checks whether a Centrifuge pool is active - can be used to prevent deposit / redemption requests to/from certain pools & avoid transfers from escrow related to inactive pools. + modifier poolActive(address _liquidityPool) { + LiquidityPoolInfo storage lp = addressToLiquidityPoolInfo[_liquidityPool]; + require(pools[lp.poolId].isActive, "CentrifugeConnector/pool-deactivated"); _; } - modifier connectorsActive() { - require(gateway.active(), "CentrifugeConnector/connectors-deactivated"); + /// @dev checks whether gateway is active - can be used to prevent any interactions with centrifuge chain and stop all deposits & redemtions from escrow. + modifier connectorActive() { + require(gateway.active(), "CentrifugeConnector/connector-deactivated"); _; } @@ -144,53 +156,128 @@ contract CentrifugeConnector is Auth { emit File(what, data); } - // --- Outgoing message handling --- - // auth functions - function processDeposit(address _tranche, address _user, uint256 _currencyAmount) public poolActive(_tranche) connectorsActive auth returns (uint256) { - require((_currencyAmount <= orderbook[_user][_tranche].maxDeposit), "CentrifugeConnector/amount-exceeds-deposit-limits"); - uint256 userTrancheTokenPrice = calcCustomTrancheTokenPrice( _user, _tranche); - require((userTrancheTokenPrice > 0), "Tranche4626/amount-exceeds-deposit-limits"); - uint256 trancheTokensPayout = _currencyAmount / userTrancheTokenPrice; - _decreaseDepositLimits(_user, _tranche, _currencyAmount, trancheTokensPayout); // decrease the possible deposit limits - require(TrancheLike(_tranche).transferFrom(address(escrow), _user, trancheTokensPayout), "CentrifugeConnector/trancheTokens-transfer-failed"); - return trancheTokensPayout; - } - - function processMint(address _tranche, address _user, uint256 _trancheTokensAmount) public poolActive(_tranche) connectorsActive auth returns (uint256) { - require((_trancheTokensAmount <= orderbook[_user][_tranche].maxMint), "CentrifugeConnector/amount-exceeds-mint-limits"); - uint256 userTrancheTokenPrice = calcCustomTrancheTokenPrice( _user, _tranche); - require((userTrancheTokenPrice > 0), "Tranche4626/amount-exceeds-mint-limits"); - uint256 currencyDeposited = _trancheTokensAmount * userTrancheTokenPrice; - _decreaseDepositLimits(_user, _tranche, currencyDeposited, _trancheTokensAmount); // decrease the possible deposit limits - require(TrancheLike(_tranche).transferFrom(address(escrow), _user, _trancheTokensAmount), "CentrifugeConnector/shares-transfer-failed"); - return currencyDeposited; - } - - function processWithdraw(address _tranche, uint256 _currencyAmount, address _receiver, address _user) public poolActive(_tranche) connectorsActive auth returns (uint256) { - require((_currencyAmount <= orderbook[_user][_tranche].maxWithdraw), "CentrifugeConnector/amount-exceeds-withdraw-limits"); - TrancheLike tranche = TrancheLike(_tranche); - uint256 userTrancheTokenPrice = calcCustomTrancheTokenPrice(_user, _tranche); - require((userTrancheTokenPrice > 0), "Tranche4626/amount-exceeds-withdraw-limits"); - uint256 redeemedTrancheTokens = _currencyAmount / userTrancheTokenPrice; - _decreaseRedemptionLimits(_user, _tranche, _currencyAmount, redeemedTrancheTokens); - require(ERC20Like(tranche.asset()).transferFrom(address(escrow), _receiver, _currencyAmount), "CentrifugeConnector/trancheTokens-transfer-failed"); - return redeemedTrancheTokens; - } - - function processRedeem(address _tranche, uint256 _trancheTokensAmount, address _receiver, address _user) public poolActive(_tranche) connectorsActive auth returns (uint256) { - require((_trancheTokensAmount <= orderbook[_user][_tranche].maxRedeem), "CentrifugeConnector/amount-exceeds-redeem-limits"); - uint256 userTrancheTokenPrice = calcCustomTrancheTokenPrice( _user, _tranche); - require((userTrancheTokenPrice > 0), "Tranche4626/amount-exceeds-redemption-limits"); - uint256 currencyPayout = _trancheTokensAmount * userTrancheTokenPrice; - _decreaseRedemptionLimits(_user, _tranche, currencyPayout, _trancheTokensAmount); // decrease the possible deposit limits - require(TrancheLike(_tranche).transferFrom(address(escrow), _receiver, currencyPayout), "CentrifugeConnector/shares-transfer-failed"); - return currencyPayout; - } - - function requestRedeem(address _tranche, uint256 _trancheTokensAmount, address _user) connectorsActive poolActive(_tranche) public auth { - UserTrancheValues memory userValues = orderbook[_user][_tranche]; - CentrifugeTranche memory cTranche = centrifugeTranches[_tranche]; - TrancheLike tranche = TrancheLike(_tranche); + /// @dev activate / deactivate pool + function setPoolActive(uint64 _poolId, bool _isActive) external auth { + Pool storage pool = pools[_poolId]; + require(pool.createdAt > 0, "CentrifugeConnector/invalid-pool"); + pool.isActive = _isActive; + } + + // --- Liquidity Pool Function --- + /// @dev calculates the avg share price for the deposited assets of a specific user + /// @dev + /// @return currencyAmount is type of uin256 to support the EIP4626 Liquidity Pool interface + function maxDeposit(address _user, address _liquidityPool) public view returns (uint256 currencyAmount) { + currencyAmount = uint256(orderbook[_user][_liquidityPool].maxDeposit); + } + + /// @dev + /// @return trancheTokenAmount type of uin256 to support the EIP4626 Liquidity Pool interface + function maxMint(address _user, address _liquidityPool) public view returns (uint256 trancheTokenAmount) { + trancheTokenAmount = uint256(orderbook[_user][_liquidityPool].maxMint); + } + + /// @dev + /// @return currencyAmount type of uin256 to support the EIP4626 Liquidity Pool interface + function maxWithdraw(address _user, address _liquidityPool) public view returns (uint256 currencyAmount) { + currencyAmount = uint256(orderbook[_user][_liquidityPool].maxWithdraw); + } + + /// @dev + /// @return trancheTokenAmount type of uin256 to support the EIP4626 Liquidity Pool interface + function maxRedeem(address _user, address _liquidityPool) public view returns (uint256 trancheTokenAmount) { + trancheTokenAmount = uint256(orderbook[_user][_liquidityPool].maxRedeem); + } + + /// @dev processes user's currency deposit / investment after the epoch has been executed on Centrifuge chain. + /// In case user's invest order was fullfilled on Centrifuge chain during epoch execution MaxDeposit and MaxMint are increased and trancheTokens can be transferred to user's wallet on calling processDeposit. + /// Note: The currency required to fullfill the invest order is already locked in escrow upon calling requestDeposit. + /// @notice trancheTokenAmount return value is type of uint256 to be compliant with EIP4626 LiquidityPool interface + /// @return trancheTokenAmount the amount of trancheTokens transferred to the user's wallet after successful depoit + function processDeposit(address _liquidityPool, address _user, uint256 _currencyAmount) public poolActive(_liquidityPool) connectorActive auth returns (uint256) { + uint128 currencyAmount = _toUint128(_currencyAmount); + LiquidityPoolLike lPool = LiquidityPoolLike(_liquidityPool); + + require((currencyAmount <= orderbook[_user][_liquidityPool].maxDeposit), "CentrifugeConnector/amount-exceeds-deposit-limits"); + uint128 userTrancheTokenPriceLP = calcCustomTrancheTokenPrice( _user, _liquidityPool); + require((userTrancheTokenPriceLP > 0), "LiquidityPool/amount-exceeds-deposit-limits"); + uint128 trancheTokenAmount = currencyAmount / userTrancheTokenPriceLP; + + _decreaseDepositLimits(_user, _liquidityPool, currencyAmount, trancheTokenAmount); // decrease user's deposit limits for this lp + require(lPool.hasMember( _user), "CentrifugeConnector/trancheTokens-not-a-member"); + require(lPool.transferFrom(address(escrow), _user, trancheTokenAmount), "CentrifugeConnector/trancheTokens-transfer-failed"); + + emit DepositProcessed(_liquidityPool, _user, currencyAmount); + return uint256(trancheTokenAmount); + } + + /// @dev processes user's currency deposit / investment after the epoch has been executed on Centrifuge chain. + /// In case user's invest order was fullfilled on Centrifuge chain during epoch execution MaxDeposit and MaxMint are increased and trancheTokens can be transferred to user's wallet on calling processDeposit or processMint. + /// Note: The currency amount required to fullfill the invest order is already locked in escrow upon calling requestDeposit. + /// Note: The tranche tokens are already minted on collectInvest and are deposited to the escrow account until the users calls mint, or deposit. + /// @notice currencyAmount return value is type of uint256 to be compliant with EIP4626 LiquidityPool interface + /// @return currencyAmount the amount of liquidityPool assets invested and locked in escrow in order for the amount of tranche received after successful investment into the pool. + function processMint(address _liquidityPool, address _user, uint256 _trancheTokenAmount) public poolActive(_liquidityPool) connectorActive auth returns (uint256) { + uint128 trancheTokenAmount = _toUint128(_trancheTokenAmount); + LiquidityPoolLike lPool = LiquidityPoolLike(_liquidityPool); + + require((trancheTokenAmount <= orderbook[_user][ _liquidityPool].maxMint), "CentrifugeConnector/amount-exceeds-mint-limits"); + uint128 userTrancheTokenPriceLP = calcCustomTrancheTokenPrice(_user, _liquidityPool); + require((userTrancheTokenPriceLP > 0), "LiquidityPool/amount-exceeds-mint-limits"); + uint128 currencyAmount = trancheTokenAmount * userTrancheTokenPriceLP; + + _decreaseDepositLimits(_user, _liquidityPool, currencyAmount, trancheTokenAmount); // decrease the possible deposit limits + require(lPool.hasMember( _user), "CentrifugeConnector/trancheTokens-not-a-member"); + require(lPool.transferFrom(address(escrow), _user, trancheTokenAmount), "CentrifugeConnector/trancheTokens-transfer-failed"); + + emit DepositProcessed(_liquidityPool, _user, currencyAmount); + return uint256(currencyAmount); + } + + /// @dev processes user's trancheToken redemption after the epoch has been executed on Centrifuge chain. + /// In case user's redempion order was fullfilled on Centrifuge chain during epoch execution MaxRedeem and MaxWithdraw are increased and LiquidityPool currency can be transferred to user's wallet on calling processRedeem or processWithdraw. + /// Note: The trancheToken amount required to fullfill the redemption order was already locked in escrow upon calling requestRedeem and burned upon collectRedeem. + /// @notice currencyAmount return value is type of uint256 to be compliant with EIP4626 LiquidityPool interface + /// @return currencyAmount the amount of liquidityPool assets received for the amount of redeemed/burned trancheTokens. + function processRedeem(address _liquidityPool, uint256 _trancheTokenAmount, address _receiver, address _user) public poolActive(_liquidityPool) connectorActive auth returns (uint256) { + uint128 trancheTokenAmount = _toUint128(_trancheTokenAmount); + LiquidityPoolLike lPool = LiquidityPoolLike(_liquidityPool); + + require((trancheTokenAmount <= orderbook[_user][ _liquidityPool].maxRedeem), "CentrifugeConnector/amount-exceeds-redeem-limits"); + uint128 userTrancheTokenPriceLP = calcCustomTrancheTokenPrice(_user, _liquidityPool); + require((userTrancheTokenPriceLP > 0), "LiquidityPool/amount-exceeds-redemption-limits"); + uint128 currencyAmount = trancheTokenAmount * userTrancheTokenPriceLP; + + _decreaseRedemptionLimits(_user, _liquidityPool, currencyAmount, trancheTokenAmount); // decrease the possible deposit limits + require(ERC20Like(lPool.asset()).transferFrom(address(escrow), _receiver, currencyAmount), "CentrifugeConnector/shares-transfer-failed"); + + emit RedemptionProcessed(_liquidityPool, _user, trancheTokenAmount); + return uint256(currencyAmount); + } + + /// @dev processes user's trancheToken redemption after the epoch has been executed on Centrifuge chain. + /// In case user's redempion order was fullfilled on Centrifuge chain during epoch execution MaxRedeem and MaxWithdraw are increased and LiquidityPool currency can be transferred to user's wallet on calling processRedeem or processWithdraw. + /// Note: The trancheToken amount required to fullfill the redemption order was already locked in escrow upon calling requestRedeem and burned upon collectRedeem. + /// @notice trancheTokenAmount return value is type of uint256 to be compliant with EIP4626 LiquidityPool interface + /// @return trancheTokenAmount the amount of trancheTokens redeemed/burned required to receive the currencyAmount payout/withdrawel. + function processWithdraw(address _liquidityPool, uint256 _currencyAmount, address _receiver, address _user) public poolActive(_liquidityPool) connectorActive auth returns (uint256) { + uint128 currencyAmount = _toUint128(_currencyAmount); + LiquidityPoolLike lPool = LiquidityPoolLike(_liquidityPool); + + require((currencyAmount <= orderbook[_user][ _liquidityPool].maxWithdraw), "CentrifugeConnector/amount-exceeds-withdraw-limits"); + uint128 userTrancheTokenPriceLP = calcCustomTrancheTokenPrice(_user, _liquidityPool); + require((userTrancheTokenPriceLP > 0), "LiquidityPool/amount-exceeds-withdraw-limits"); + uint128 trancheTokenAmount = currencyAmount / userTrancheTokenPriceLP; + + _decreaseRedemptionLimits(_user, _liquidityPool, currencyAmount, trancheTokenAmount); + require(ERC20Like(lPool.asset()).transferFrom(address(escrow), _receiver, currencyAmount), "CentrifugeConnector/trancheTokens-transfer-failed"); + return uint256(trancheTokenAmount); + } + + function requestRedeem(address _liquidityPool, uint256 _trancheTokensAmount, address _user) connectorActive poolActive(_liquidityPool) public auth { + LPValues memory userValues = orderbook[_user][ _liquidityPool]; + Tranche memory cTranche = tranches[ _liquidityPool]; + LiquidityPoolLike tranche = LiquidityPoolLike( _liquidityPool); require(_poolCurrencyCheck(cTranche.poolId, tranche.asset()), "CentrifugeConnector/currency-not-supported"); require(_trancheTokenCheck(cTranche.poolId, cTranche.trancheId, _user), "CentrifugeConnector/tranche-tokens-not-supported"); @@ -204,9 +291,9 @@ contract CentrifugeConnector is Auth { } if(userValues.maxMint >= _trancheTokensAmount) { // case: user has unclaimed trancheTokens in escrow -> more than redemption request - uint256 userTrancheTokenPrice = calcCustomTrancheTokenPrice( _user, _tranche); + uint256 userTrancheTokenPrice = calcCustomTrancheTokenPrice( _user, _liquidityPool); uint256 assets = _trancheTokensAmount * userTrancheTokenPrice; - _decreaseDepositLimits(_user, _tranche, assets, _trancheTokensAmount); + _decreaseDepositLimits(_user, _liquidityPool, assets, _trancheTokensAmount); } else { uint transferAmount = _trancheTokensAmount - userValues.maxMint; userValues.maxDeposit = 0; @@ -219,13 +306,11 @@ contract CentrifugeConnector is Auth { gateway.increaseRedeemOrder(cTranche.poolId, cTranche.trancheId, _user, currencyAddressToId[tranche.asset()], uint128(_trancheTokensAmount)); } - - // TODO: fix uint256 - uint128 - function requestDeposit(address _tranche, uint _currencyAmount, address _user) connectorsActive poolActive(_tranche) public auth { - UserTrancheValues memory userValues = orderbook[_user][_tranche]; - CentrifugeTranche memory cTranche = centrifugeTranches[_tranche]; - TrancheLike tranche = TrancheLike(_tranche); - ERC20Like currency = ERC20Like(TrancheLike(_tranche).asset()); + function requestDeposit(address _liquidityPool, uint _currencyAmount, address _user) connectorActive poolActive(_liquidityPool) public auth { + LPValues memory userValues = orderbook[_user][ _liquidityPool]; + Tranche memory cTranche = tranches[ _liquidityPool]; + LiquidityPoolLike tranche = LiquidityPoolLike( _liquidityPool); + ERC20Like currency = ERC20Like(LiquidityPoolLike( _liquidityPool).asset()); require(_poolCurrencyCheck(cTranche.poolId, tranche.asset()), "CentrifugeConnector/currency-not-supported"); require(_trancheTokenCheck(cTranche.poolId, cTranche.trancheId, _user), "CentrifugeConnector/tranche-tokens-not-supported"); @@ -237,9 +322,9 @@ contract CentrifugeConnector is Auth { return; } if(userValues.maxWithdraw >= _currencyAmount) { // case: user has some claimable fund in escrow -> funds > Deposit request - uint256 userTrancheTokenPrice = calcCustomTrancheTokenPrice( _user, _tranche); + uint256 userTrancheTokenPrice = calcCustomTrancheTokenPrice( _user, _liquidityPool); uint256 trancheTokens = _currencyAmount / userTrancheTokenPrice; - _decreaseRedemptionLimits(_user, _tranche, _currencyAmount, trancheTokens); + _decreaseRedemptionLimits(_user, _liquidityPool, _currencyAmount, trancheTokens); } else { uint transferAmount = _currencyAmount - userValues.maxWithdraw; userValues.maxWithdraw = 0; @@ -269,11 +354,11 @@ contract CentrifugeConnector is Auth { bytes32 destinationAddress, uint128 amount ) public { - TrancheLike tranche = TrancheLike(tranches[poolId][trancheId]); - require(address(tranche) != address(0), "CentrifugeConnector/unknown-token"); + LiquidityPoolLike lPool = LiquidityPoolLike(liquidityPools[poolId][trancheId]); + require(address(lPool) != address(0), "CentrifugeConnector/unknown-token"); - require(tranche.balanceOf(msg.sender) >= amount, "CentrifugeConnector/insufficient-balance"); - tranche.burn(msg.sender, amount); + require(lPool.balanceOf(msg.sender) >= amount, "CentrifugeConnector/insufficient-balance"); + lPool.burn(msg.sender, amount); gateway.transferTrancheTokensToCentrifuge(poolId, trancheId, msg.sender, destinationAddress, amount); } @@ -285,7 +370,7 @@ contract CentrifugeConnector is Auth { address destinationAddress, uint128 amount ) public { - TrancheLike tranche = TrancheLike(tranches[poolId][trancheId]); + LiquidityPoolLike tranche = LiquidityPoolLike(liquidityPools[poolId][trancheId]); require(address(tranche) != address(0), "CentrifugeConnector/unknown-token"); require(tranche.balanceOf(msg.sender) >= amount, "CentrifugeConnector/insufficient-balance"); @@ -297,7 +382,7 @@ contract CentrifugeConnector is Auth { } function collectInvest(uint64 _poolId, bytes16 _trancheId) public { - TrancheLike tranche = TrancheLike(tranches[_poolId][_trancheId]); + LiquidityPoolLike tranche = LiquidityPoolLike(liquidityPools[_poolId][_trancheId]); require(address(tranche) != address(0), "CentrifugeConnector/unknown-tranche-token"); require(tranche.hasMember(msg.sender), "CentrifugeConnector/not-a-member"); @@ -305,7 +390,7 @@ contract CentrifugeConnector is Auth { } function collectRedeem(uint64 _poolId, bytes16 _trancheId) public { - TrancheLike tranche = TrancheLike(tranches[_poolId][_trancheId]); + LiquidityPoolLike tranche = LiquidityPoolLike(liquidityPools[_poolId][_trancheId]); require(address(tranche) != address(0), "CentrifugeConnector/unknown-tranche-token"); require(tranche.hasMember(msg.sender), "CentrifugeConnector/not-a-member"); @@ -348,34 +433,31 @@ contract CentrifugeConnector is Auth { string memory _tokenName, string memory _tokenSymbol, uint8 _decimals, - uint128 _price + uint128 _price // not required here ) public onlyGateway { Pool storage pool = pools[_poolId]; require(pool.createdAt > 0, "CentrifugeConnector/invalid-pool"); - - address tranche = tranches[_poolId][_trancheId]; - require(tranche == address(0), "CentrifugeConnector/tranche-already-added"); - address asset = address(0x6B175474E89094C44Da98b954EedeAC495271d0F); // TODO FIX : provide tranche currency / assets : default DAI? - tranche = deployTranche(_poolId, _trancheId, asset, _decimals, _tokenName, _tokenSymbol); - TrancheLike(tranche).updateTokenPrice(_price); - - // update multi-chain tranche mappings - tranches[_poolId][_trancheId] = tranche; - CentrifugeTranche memory cTranche = centrifugeTranches[tranche]; - cTranche.poolId = _poolId; - cTranche.trancheId = _trancheId; + Tranche storage tranche = tranches[_poolId][_trancheId]; + require(tranche.createdAt == 0, "CentrifugeConnector/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 updateTokenPrice(uint64 _poolId, bytes16 _trancheId, uint128 _price) public onlyGateway { - address token = tranches[_poolId][_trancheId]; + address token = liquidityPools[_poolId][_trancheId]; require(token != address(0), "CentrifugeConnector/invalid-pool-or-tranche"); - TrancheLike(token).updateTokenPrice(_price); + LiquidityPoolLike(token).updateTokenPrice(_price); } function updateMember(uint64 _poolId, bytes16 _trancheId, address _user, uint64 _validUntil) public onlyGateway { - TrancheLike tranche = TrancheLike(tranches[_poolId][_trancheId]); + LiquidityPoolLike tranche = LiquidityPoolLike(liquidityPools[_poolId][_trancheId]); require(address(tranche) != address(0), "CentrifugeConnector/invalid-pool-or-tranche"); MemberlistLike memberlist = MemberlistLike(tranche.memberlist()); memberlist.updateMember(_user, _validUntil); @@ -396,7 +478,7 @@ contract CentrifugeConnector is Auth { public onlyGateway { - TrancheLike tranche = TrancheLike(tranches[_poolId][_trancheId]); + LiquidityPoolLike tranche = LiquidityPoolLike(liquidityPools[_poolId][_trancheId]); require(address(tranche) != address(0), "CentrifugeConnector/unknown-token"); require(tranche.hasMember(_destinationAddress), "CentrifugeConnector/not-a-member"); @@ -406,7 +488,7 @@ contract CentrifugeConnector is Auth { function handleDecreaseInvestOrder(uint64 _poolId, bytes16 _trancheId, address _user, uint128 _currency, uint128 _currencyPayout, uint128 _remainingInvestOrder) public onlyGateway { require(_currencyPayout != 0, "CentrifugeConnector/zero-payout"); address currencyAddress = currencyIdToAddress[_currency]; - TrancheLike tranche = TrancheLike(tranches[_poolId][_trancheId]); + LiquidityPoolLike tranche = LiquidityPoolLike(liquidityPools[_poolId][_trancheId]); require(address(tranche) != address(0), "CentrifugeConnector/tranche-does-not-exist"); require(allowedPoolCurrencies[_poolId][currencyAddress], "CentrifugeConnector/pool-currency-not-allowed"); require(currencyAddress != address(0), "CentrifugeConnector/unknown-currency"); @@ -422,13 +504,12 @@ contract CentrifugeConnector is Auth { orderbook[_user][address(tranche)].openInvest = _remainingInvestOrder; } - //TODO: currency not really required here function handleDecreaseRedeemOrder(uint64 _poolId, bytes16 _trancheId, address _user, uint128 _currency, uint128 _tokensPayout, uint128 _remainingRedeemOrder) public onlyGateway { require(_tokensPayout != 0, "CentrifugeConnector/zero-payout"); - TrancheLike tranche = TrancheLike(tranches[_poolId][_trancheId]); + LiquidityPoolLike tranche = LiquidityPoolLike(liquidityPools[_poolId][_trancheId]); require(address(tranche) != address(0), "CentrifugeConnector/tranche-does-not-exist"); - require(TrancheLike(tranche).hasMember(_user), "CentrifugeConnector/not-a-member"); + require(LiquidityPoolLike(tranche).hasMember(_user), "CentrifugeConnector/not-a-member"); // TODO: escrow should give max approval on deployment EscrowLike(escrow).approve(address(tranche), address(this), _tokensPayout); require( @@ -440,31 +521,68 @@ contract CentrifugeConnector is Auth { function handleCollectInvest(uint64 _poolId, bytes16 _trancheId, address _recepient, uint128 _currency, uint128 _currencyInvested, uint128 _tokensPayout, uint128 _remainingInvestOrder) public onlyGateway { require(_currencyInvested != 0, "CentrifugeConnector/zero-invest"); - address tranche = tranches[_poolId][_trancheId]; + address tranche = liquidityPools[_poolId][_trancheId]; require(tranche != address(0), "CentrifugeConnector/tranche-does-not-exist"); - UserTrancheValues memory values = orderbook[_recepient][tranche]; + LPValues memory values = orderbook[_recepient][tranche]; values.openInvest = _remainingInvestOrder; values.maxDeposit = values.maxDeposit + _currencyInvested; values.maxMint = values.maxMint + _tokensPayout; - TrancheLike(tranche).mint(address(escrow), _tokensPayout); // mint to escrow. Recepeint can claim by calling withdraw / redeem + LiquidityPoolLike(tranche).mint(address(escrow), _tokensPayout); // mint to escrow. Recepeint can claim by calling withdraw / redeem } function handleCollectRedeem(uint64 _poolId, bytes16 _trancheId, address _recepient, uint128 _currency, uint128 _currencyPayout, uint128 _trancheTokensRedeemed, uint128 _remainingRedeemOrder) public onlyGateway { require(_trancheTokensRedeemed != 0, "CentrifugeConnector/zero-redeem"); - address tranche = tranches[_poolId][_trancheId]; + address tranche = liquidityPools[_poolId][_trancheId]; require(tranche != address(0), "CentrifugeConnector/tranche-does-not-exist"); - UserTrancheValues memory values = orderbook[_recepient][tranche]; + LPValues memory values = orderbook[_recepient][tranche]; values.openRedeem = _remainingRedeemOrder; values.maxWithdraw = values.maxWithdraw + _currencyPayout; values.maxRedeem = values.maxRedeem + _trancheTokensRedeemed; - TrancheLike(tranche).burn(address(escrow), _trancheTokensRedeemed); // burned redeemed tokens from escrow - } + LiquidityPoolLike(tranche).burn(address(escrow), _trancheTokensRedeemed); // burned redeemed tokens from escrow + } + + // ----- public functions - // ------ internal helper functions + function deployLiquidityPool( + uint64 _poolId, + bytes16 _trancheId, + address _currency + ) public returns (address) { + + address liquidityPool = liquidityPools[_poolId][_trancheId][_currency]; + require(liquidityPool == address(0), "CentrifugeConnector/liquidityPool-already-deployed"); + Tranche storage tranche = tranches[_poolId][_trancheId]; + require(tranche.createdAt != 0, "CentrifugeConnector/tranche-does-not-exist"); // tranche must have been added + require(_poolCurrencyCheck(_poolId, _currency), "CentrifugeConnector/currency-not-supported"); // currency must be supported by pool + uint128 currencyId = currencyAddressToId[_currency]; + // gateway admin on liquidityPool + liquidityPool = liquidityPoolFactory.newLiquidityPool(_poolId, _trancheId, currencyId, _currency, address(this), address(gateway), tranche.tokenName, tranche.tokenSymbol, tranche.decimals); + + liquidityPools[_poolId][_trancheId][_currency] = liquidityPool; + LiquidityPoolInfo storage lPoolInfo = addressToLiquidityPoolInfo[liquidityPool]; + lPoolInfo.poolId = _poolId; + lPoolInfo.trancheId = _trancheId; + + address memberlist = memberlistFactory.newMemberlist(address(gateway)); // gateway admin on memberlist + LiquidityPoolLike(liquidityPool).file("memberlist", memberlist); + MemberlistLike(memberlist).updateMember(address(escrow), type(uint256).max); // add escrow to tranche tokens memberlist + + emit LiquidityPoolDeployed(_poolId, _trancheId, liquidityPool); + return liquidityPool; + } + + // ------ helper functions + function calcCustomTrancheTokenPrice(address _user, address _liquidityPool) public view returns (uint128 userTrancheTokenPrice) { + LPValues memory lpValues = orderbook[_user][_liquidityPool]; + if(lpValues.maxMint == 0) { + return 0; + } + userTrancheTokenPrice = lpValues.maxDeposit / lpValues.maxMint; + } function _poolCurrencyCheck(uint64 _poolId, address _currencyAddress) internal view returns (bool) { uint128 currency = currencyAddressToId[_currencyAddress]; @@ -474,14 +592,14 @@ contract CentrifugeConnector is Auth { } function _trancheTokenCheck(uint64 _poolId, bytes16 _trancheId, address _user) internal returns (bool) { - TrancheLike tranche = TrancheLike(tranches[_poolId][_trancheId]); + LiquidityPoolLike tranche = LiquidityPoolLike(liquidityPools[_poolId][_trancheId]); require(address(tranche) != address(0), "CentrifugeConnector/unknown-tranche-token"); require(tranche.hasMember(_user), "CentrifugeConnector/not-a-member"); return true; } - function _decreaseDepositLimits(address _user, address _tranche, uint256 _currency, uint256 _trancheTokens) internal { - UserTrancheValues storage values = orderbook[_user][_tranche]; + function _decreaseDepositLimits(address _user, address _liquidityPool, uint128 _currency, uint128 _trancheTokens) internal { + LPValues storage values = orderbook[_user][_liquidityPool]; if (values.maxDeposit < _currency) { values.maxDeposit = 0; } else { @@ -494,8 +612,8 @@ contract CentrifugeConnector is Auth { } } - function _decreaseRedemptionLimits(address _user, address _tranche, uint256 _currency, uint256 _trancheTokens) internal { - UserTrancheValues storage values = orderbook[_user][_tranche]; + function _decreaseRedemptionLimits(address _user, address _liquidityPool, uint128 _currency, uint128 _trancheTokens) internal { + LPValues storage values = orderbook[_user][_liquidityPool]; if (values.maxWithdraw < _currency) { values.maxDeposit = 0; } else { @@ -507,52 +625,14 @@ contract CentrifugeConnector is Auth { values.maxRedeem = values.maxRedeem - _trancheTokens; } } - - // TODO: ward setup on tranche contract - function deployTranche( - uint64 _poolId, - bytes16 _trancheId, - address _asset, - uint8 _decimals, - string memory _tokenName, - string memory _tokenSymbol) internal returns (address) { - require(tranches[_poolId][_trancheId] == address(0), "CentrifugeConnector/tranche-already-deployed"); - Tranche4626 tranche = new Tranche4626(_asset, address(this), _decimals, _tokenName, _tokenSymbol); // TODO: use factory - address tranche_ = address(tranche); - tranches[_poolId][_trancheId] = tranche_; - - address memberlist = memberlistFactory.newMemberlist(); - TrancheLike(tranche_).file("memberlist", memberlist); - MemberlistLike(memberlist).updateMember(address(escrow), type(uint256).max); // add escrow to tranche tokens memberlist - emit TrancheDeployed(_poolId, _trancheId, tranche_); - return tranche_; - } - // ------ EIP 4626 view functions - - /// @dev calculates the avg share price for the deposited assets of a specific user - function calcCustomTrancheTokenPrice(address _user, address _tranche) public view returns (uint256 userTrancheTokenPrice) { - UserTrancheValues memory values = orderbook[_user][_tranche]; - if(values.maxMint == 0) { - return 0; + /// @dev safe type conversion from uint256 to uint128. Revert if value is too big to be stored with uint128. Avoid data loss. + /// @return value - safely converted without data loss + function _toUint128(uint256 _value) internal returns (uint128 value) { + if (_value > 2 ** 128) { + revert(); + } else { + value = uint128(_value); } - userTrancheTokenPrice = values.maxDeposit / values.maxMint; } - - function maxDeposit(address _user, address _tranche) public view returns (uint256) { - return orderbook[_user][_tranche].maxDeposit; - } - - function maxMint(address _user, address _tranche) public view returns (uint256) { - return orderbook[_user][_tranche].maxMint; - } - - function maxWithdraw(address _user, address _tranche) public view returns (uint256) { - return orderbook[_user][_tranche].maxWithdraw; - } - - function maxRedeem(address _user, address _tranche) public view returns (uint256) { - // UserTrancheValues v = orderbook[_user][_tranche]; - return orderbook[_user][_tranche].maxRedeem; - } } diff --git a/src/liquidityPool/Factory.sol b/src/liquidityPool/Factory.sol new file mode 100644 index 00000000..15be44a4 --- /dev/null +++ b/src/liquidityPool/Factory.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.18; + +import { LiquidityPool } from "./LiquidityPool.sol"; +import { Memberlist} from "../token/memberlist.sol"; + +interface ImmutableCreate2Factory { + function safeCreate2(bytes32 salt, bytes calldata initCode) external payable returns (address deploymentAddress); +} + +interface LiquidityPoolFactoryLike { + function newLiquidityPool(uint64 _poolId, bytes16 _trancheId, uint _currencyId, address _asset, address _connector, address _admin, string memory _name, string memory _symbol, uint8 _decimals) external returns (address); +} + +contract LiquidityPoolFactory { + function newLiquidityPool(uint64 _poolId, bytes16 _trancheId, uint _currencyId, address _asset, address _connector, address _admin, string memory _name, string memory _symbol, uint8 _decimals) + public + returns (address) + { + // Salt is hash(poolId + trancheId + asset), to deploy copies of the restricted token contract + // on multiple chains with the same address for the same tranche + bytes32 salt = keccak256(abi.encodePacked(_poolId, _trancheId, _currencyId)); + + LiquidityPool lPool = new LiquidityPool{salt: salt}(_decimals); + + // Name and symbol are not passed on constructor, such that if the same tranche is deployed + // on another chain with a different name (it might have changed in between deployments), + // then the address remains deterministic. + lPool.file("name", _name); + lPool.file("symbol", _symbol); + lPool.file("connector", _connector); + lPool.file("asset", _asset); + + + lPool.deny(msg.sender); + lPool.deny(address(this)); + lPool.rely(_admin); + return address(lPool); + } +} + +interface MemberlistFactoryLike { + function newMemberlist(address _admin) external returns (address); +} + +contract MemberlistFactory { + function newMemberlist(address _admin) public returns (address memberList) { + Memberlist memberlist = new Memberlist(); + + memberlist.deny(msg.sender); + memberlist.deny(address(this)); + memberlist.rely(_admin); + + return (address(memberlist)); + } +} diff --git a/src/Tranche4626.sol b/src/liquidityPool/LiquidityPool.sol similarity index 86% rename from src/Tranche4626.sol rename to src/liquidityPool/LiquidityPool.sol index 1e7bfd9d..367d40b7 100644 --- a/src/Tranche4626.sol +++ b/src/liquidityPool/LiquidityPool.sol @@ -12,7 +12,7 @@ pragma solidity ^0.8.18; // Latest Tranche values, like share / token price, tranche asset value, total assets... have to be retrieved from Centrifuge chain in order to provide share <-> asset conversions. // 2. Pool Epochs: Deposits into and redemptions from Centrifuge Pools are subject to epochs. Deposit and redemption orders are collected during 24H epoch periods // and filled during epoch execution following the rules of the underlying pool. Consequently, deposits and redemptions are not instanty possible and have to follow the epoch schedule. -// Tranche4626 is extending the EIP4626 standard by 'requestRedeem' & 'requestDeposit' functions, where redeem and deposit orders are submitted to the pools to be included in the execution of the following epoch. +// LiquidityPool is extending the EIP4626 standard by 'requestRedeem' & 'requestDeposit' functions, where redeem and deposit orders are submitted to the pools to be included in the execution of the following epoch. // After execution users can use the redeem and withdraw functions to get their shares and/or assets from the pools. // other EIP4626 implementations @@ -20,14 +20,15 @@ pragma solidity ^0.8.18; // yearn: https://github.com/yearn/yearn-vaults-v3/blob/master/contracts/VaultV3.vy -import "./token/restricted.sol"; +import "../token/restricted.sol"; +import { Memberlist } from "../token/memberlist.sol"; interface ConnectorLike { function processDeposit(address _tranche, address _receiver, uint256 _assets) external returns (uint256); function processMint(address _tranche, address _receiver, uint256 _shares) external returns (uint256); function processWithdraw(address _tranche, uint256 _assets, address _receiver, address _owner) external returns (uint256); - function maxDeposit(address _user, address _tranche) external view returns (uint256); - function maxMint(address _user, address _tranche) external view returns (uint256); + function maxDeposit(address _user, address _tranche) external view returns (uint256); + function maxMint(address _user, address _tranche) external view returns (uint256); function maxWithdraw(address _user, address _tranche) external view returns (uint256); function maxRedeem(address _user, address _tranche) external view returns (uint256); function requestRedeem(address _tranche, uint256 _shares, address _receiver) external; @@ -35,47 +36,47 @@ interface ConnectorLike { } -/// @title Tranche4626 +/// @title LiquidityPool /// @author ilinzweilin -contract Tranche4626 is RestrictedToken { +contract LiquidityPool is RestrictedToken { ConnectorLike public connector; - address public asset; // underlying stable ERC-20 stable currency. - uint256 public maxAssetDeposit = 2 ** 256 - 1; // max stable currency deposit into the tranche -> default: no limit. + address public asset; // underlying stable ERC-20 stable currency + uint256 public maxAssetDeposit = 2 ** 256 - 1; // max stable currency deposit into the tranche -> default: no limit uint128 latestPrice; // lates share / token price uint256 lastPriceUpdate; // timestamp of the latest share / token price update - // ERC4626 events + // events event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares); event Withdraw(address indexed sender, address indexed receiver, address indexed owner, uint256 assets, uint256 shares); + event File(bytes32 indexed what, address indexed data); - constructor(address _asset, address _connector, uint8 _decimals, string memory _name, string memory _symbol) RestrictedToken(_decimals) { - name = _name; - symbol = _symbol; - asset = _asset; - connector = ConnectorLike(_connector); + constructor(uint8 _decimals) RestrictedToken(_decimals) {} - wards[_connector] = 1; - emit Rely(msg.sender); + /// @dev connector and asset address to be filed by the factory on deployment + function file(bytes32 _what, address _data) override public auth { + if (_what == "connector") connector = ConnectorLike(_data); + else if (_what == "asset") asset = _data; + else if (_what == "memberlist") memberlist = MemberlistLike(_data); + else revert("LiquidityPool/file-unrecognized-param"); + emit File(_what, _data); } - /// @dev The total amount of vault shares. - /// @return Total amount of the underlying vault assets including accrued interest. + /// @dev The total amount of vault shares + /// @return Total amount of the underlying vault assets including accrued interest function totalAssets() public view returns (uint256) { return totalSupply * latestPrice; } /// @dev Calculates the amount of shares / tranche tokens that any user would get for the amount of assets provided. The calcultion is based on the token price from the most recent epoch retrieved from Centrifuge chain. function convertToShares(uint256 _assets) public view returns (uint256 shares) { - // TODO: it should round DOWN if it’s calculating the amount of shares to issue to a user, given an amount of assets provided. shares = _assets / latestPrice; } /// @dev Calculates the asset value for an amount of shares / tranche tokens provided. The calcultion is based on the token price from the most recent epoch retrieved from Centrifuge chain. function convertToAssets(uint256 _shares) public view returns (uint256 assets) { - // TODO: it should round DOWN if it’s calculating the amount of assets to issue to a user, given an amount of shares provided. assets = _shares * latestPrice; } @@ -89,19 +90,19 @@ contract Tranche4626 is RestrictedToken { shares = convertToShares(_assets); } - /// @dev request asset deposit for a receiver to be included in the next epoch execution. Asset is locked in the escrow on request submission. + /// @dev request asset deposit for a receiver to be included in the next epoch execution. Asset is locked in the escrow on request submission function requestDeposit(uint256 _assets, address _receiver) auth public { connector.requestDeposit(address(this), _assets, _receiver); } - /// @dev collect shares for deposited funds after pool epoch execution. maxMint is the max amount of shares that can be collected. Required assets must already be locked. + /// @dev collect shares for deposited funds after pool epoch execution. maxMint is the max amount of shares that can be collected. Required assets must already be locked /// maxDeposit is the amount of funds that was successfully invested into the pool on Centrifuge chain function deposit(uint256 _assets, address _receiver) auth public returns (uint256 shares) { shares = connector.processDeposit(address(this), _receiver, _assets); emit Deposit(address(this), _receiver, _assets, shares); } - /// @dev collect shares for deposited funds after pool epoch execution. maxMint is the max amount of shares that can be collected. Required assets must already be locked. + /// @dev collect shares for deposited funds after pool epoch execution. maxMint is the max amount of shares that can be collected. Required assets must already be locked /// maxDeposit is the amount of funds that was successfully invested into the pool on Centrifuge chain function mint(uint256 _shares, address _receiver) auth public returns (uint256 assets) { assets = connector.processMint(address(this), _receiver, _shares); @@ -118,7 +119,7 @@ contract Tranche4626 is RestrictedToken { assets = convertToAssets(_shares); } - /// @dev request share redemption for a receiver to be included in the next epoch execution. Shares are locked in the escrow on request submission. + /// @dev request share redemption for a receiver to be included in the next epoch execution. Shares are locked in the escrow on request submission function requestRedeem(uint256 _shares, address _receiver) auth public { connector.requestRedeem(address(this), _shares, _receiver); } @@ -160,7 +161,6 @@ contract Tranche4626 is RestrictedToken { return currencyPayout; } - // auth functions function updateTokenPrice(uint128 _tokenPrice) public auth { latestPrice = _tokenPrice; From f60ab4ee7b9572148627fa855209e2b87be6780c Mon Sep 17 00:00:00 2001 From: ilinzweilin Date: Mon, 26 Jun 2023 10:55:20 +0100 Subject: [PATCH 14/24] fix build --- src/Connector.sol | 223 ++++++++++++++-------------- src/liquidityPool/Factory.sol | 1 + src/liquidityPool/LiquidityPool.sol | 12 +- src/token/restricted.sol | 2 +- 4 files changed, 123 insertions(+), 115 deletions(-) diff --git a/src/Connector.sol b/src/Connector.sol index b12e332e..01e577cf 100644 --- a/src/Connector.sol +++ b/src/Connector.sol @@ -51,6 +51,9 @@ interface LiquidityPoolLike { // 4626 functions function updateTokenPrice(uint128 _tokenPrice) external; function asset() external returns (address); + // centrifuge chain info functions + function poolId() external returns (uint64); + function trancheId() external returns (bytes16); } interface EscrowLike { @@ -74,12 +77,6 @@ struct Tranche { uint8 decimals; } -// /// @dev storing information about liquidity pools on EVM chain. One tranche on Centrifuge chain can have multiple corresponding liquidity pools on an EVM chain. -struct LiquidityPoolInfo { - uint64 poolId; - bytes16 trancheId; -} - /// @dev storing liquidity pool orders and deposit/redemption limits for a user struct LPValues { uint128 maxDeposit; @@ -96,7 +93,6 @@ contract CentrifugeConnector is Auth { mapping(uint64 => mapping(bytes16 => mapping(address => address))) public liquidityPools; // Centrifuge chain tranche -> currency -> liquidity pool address // Todo: add currency mapping(uint64 => mapping(bytes16 => Tranche)) public tranches; // Centrifuge chain tranches - mapping(address => LiquidityPoolInfo) public addressToLiquidityPoolInfo; // mapping(address => Tranche) public tranches; // liquidityPool -> Centrifuge chain tranches mapping(address => mapping(address => LPValues)) public orderbook; // outstanding liquidity pool orders & limits per user & liquidity pool @@ -133,8 +129,8 @@ contract CentrifugeConnector is Auth { /// @dev checks whether a Centrifuge pool is active - can be used to prevent deposit / redemption requests to/from certain pools & avoid transfers from escrow related to inactive pools. modifier poolActive(address _liquidityPool) { - LiquidityPoolInfo storage lp = addressToLiquidityPoolInfo[_liquidityPool]; - require(pools[lp.poolId].isActive, "CentrifugeConnector/pool-deactivated"); + LiquidityPoolLike lPool = LiquidityPoolLike(_liquidityPool); + require(pools[lPool.poolId()].isActive, "CentrifugeConnector/pool-deactivated"); _; } @@ -275,66 +271,65 @@ contract CentrifugeConnector is Auth { } function requestRedeem(address _liquidityPool, uint256 _trancheTokensAmount, address _user) connectorActive poolActive(_liquidityPool) public auth { - LPValues memory userValues = orderbook[_user][ _liquidityPool]; - Tranche memory cTranche = tranches[ _liquidityPool]; - LiquidityPoolLike tranche = LiquidityPoolLike( _liquidityPool); - - require(_poolCurrencyCheck(cTranche.poolId, tranche.asset()), "CentrifugeConnector/currency-not-supported"); - require(_trancheTokenCheck(cTranche.poolId, cTranche.trancheId, _user), "CentrifugeConnector/tranche-tokens-not-supported"); + LPValues storage lpValues = orderbook[_user][ _liquidityPool]; + LiquidityPoolLike lPool = LiquidityPoolLike(_liquidityPool); + uint128 trancheTokensAmount = _toUint128(_trancheTokensAmount); + + require(_poolCurrencyCheck(lPool.poolId(), lPool.asset()), "CentrifugeConnector/currency-not-supported"); + require(_trancheTokenCheck(lPool.poolId(), lPool.trancheId(), lPool.asset(), _user), "CentrifugeConnector/tranche-tokens-not-supported"); - if (userValues.openInvest > 0) { // cancel outstanding deposit orders - // replace - gateway.decreaseInvestOrder(cTranche.poolId, cTranche.trancheId, _user, currencyAddressToId[tranche.asset()], uint128(userValues.openInvest)); + if (lpValues.openInvest > 0) { // cancel outstanding deposit orders + gateway.decreaseInvestOrder(lPool.poolId(), lPool.trancheId(), _user, currencyAddressToId[lPool.asset()], uint128(lpValues.openInvest)); } - if(_trancheTokensAmount == 0) { // case: user justwants to cancel outstanding orders + if(trancheTokensAmount == 0) { // case: user only wants to cancel outstanding orders return; } - if(userValues.maxMint >= _trancheTokensAmount) { // case: user has unclaimed trancheTokens in escrow -> more than redemption request - uint256 userTrancheTokenPrice = calcCustomTrancheTokenPrice( _user, _liquidityPool); - uint256 assets = _trancheTokensAmount * userTrancheTokenPrice; - _decreaseDepositLimits(_user, _liquidityPool, assets, _trancheTokensAmount); + if(lpValues.maxMint >= trancheTokensAmount) { // case: user has unclaimed trancheTokens in escrow -> more than redemption request + uint128 userTrancheTokenPrice = calcCustomTrancheTokenPrice( _user, _liquidityPool); + uint128 assets = trancheTokensAmount * userTrancheTokenPrice; + _decreaseDepositLimits(_user, _liquidityPool, assets, trancheTokensAmount); } else { - uint transferAmount = _trancheTokensAmount - userValues.maxMint; - userValues.maxDeposit = 0; - userValues.maxMint = 0; + uint transferAmount = trancheTokensAmount - lpValues.maxMint; + lpValues.maxDeposit = 0; + lpValues.maxMint = 0; - require(tranche.balanceOf(_user) >= _trancheTokensAmount, "CentrifugeConnector/insufficient-tranche-token-balance"); - require(tranche.transferFrom(_user, address(escrow), transferAmount), "CentrifugeConnector/tranche-token-transfer-failed"); + require(lPool.balanceOf(_user) >= trancheTokensAmount, "CentrifugeConnector/insufficient-tranche-token-balance"); + require(lPool.transferFrom(_user, address(escrow), transferAmount), "CentrifugeConnector/tranche-token-transfer-failed"); } - gateway.increaseRedeemOrder(cTranche.poolId, cTranche.trancheId, _user, currencyAddressToId[tranche.asset()], uint128(_trancheTokensAmount)); + gateway.increaseRedeemOrder(lPool.poolId(), lPool.trancheId(), _user, currencyAddressToId[lPool.asset()], trancheTokensAmount); } function requestDeposit(address _liquidityPool, uint _currencyAmount, address _user) connectorActive poolActive(_liquidityPool) public auth { - LPValues memory userValues = orderbook[_user][ _liquidityPool]; - Tranche memory cTranche = tranches[ _liquidityPool]; - LiquidityPoolLike tranche = LiquidityPoolLike( _liquidityPool); - ERC20Like currency = ERC20Like(LiquidityPoolLike( _liquidityPool).asset()); + LPValues storage lpValues = orderbook[_user][ _liquidityPool]; + LiquidityPoolLike lPool = LiquidityPoolLike(_liquidityPool); + ERC20Like currency = ERC20Like(lPool.asset()); + uint128 currencyAmount = _toUint128(_currencyAmount); - require(_poolCurrencyCheck(cTranche.poolId, tranche.asset()), "CentrifugeConnector/currency-not-supported"); - require(_trancheTokenCheck(cTranche.poolId, cTranche.trancheId, _user), "CentrifugeConnector/tranche-tokens-not-supported"); + require(_poolCurrencyCheck(lPool.poolId(), lPool.asset()), "CentrifugeConnector/currency-not-supported"); + require(_trancheTokenCheck(lPool.poolId(), lPool.trancheId(), lPool.asset(), _user), "CentrifugeConnector/tranche-tokens-not-supported"); - if (userValues.openRedeem > 0) { // cancel outstanding redeem orders - gateway.decreaseRedeemOrder(cTranche.poolId, cTranche.trancheId, _user, currencyAddressToId[tranche.asset()], uint128(userValues.openRedeem)); + if (lpValues.openRedeem > 0) { // cancel outstanding redeem orders + gateway.decreaseRedeemOrder(lPool.poolId(), lPool.trancheId(), _user, currencyAddressToId[lPool.asset()], uint128(lpValues.openRedeem)); } - if(_currencyAmount == 0) { // case: user only wants to cancel outstanding redemptions + if(currencyAmount == 0) { // case: user only wants to cancel outstanding redemptions return; } - if(userValues.maxWithdraw >= _currencyAmount) { // case: user has some claimable fund in escrow -> funds > Deposit request - uint256 userTrancheTokenPrice = calcCustomTrancheTokenPrice( _user, _liquidityPool); - uint256 trancheTokens = _currencyAmount / userTrancheTokenPrice; - _decreaseRedemptionLimits(_user, _liquidityPool, _currencyAmount, trancheTokens); + if(lpValues.maxWithdraw >= currencyAmount) { // case: user has some claimable fund in escrow -> funds > Deposit request + uint128 userTrancheTokenPrice = calcCustomTrancheTokenPrice( _user, _liquidityPool); + uint128 trancheTokens = currencyAmount / userTrancheTokenPrice; + _decreaseRedemptionLimits(_user, _liquidityPool, currencyAmount, trancheTokens); } else { - uint transferAmount = _currencyAmount - userValues.maxWithdraw; - userValues.maxWithdraw = 0; - userValues.maxRedeem = 0; + uint128 transferAmount = currencyAmount - lpValues.maxWithdraw; + lpValues.maxWithdraw = 0; + lpValues.maxRedeem = 0; require(currency.balanceOf(_user) >= transferAmount, "CentrifugeConnector/insufficient-balance"); require(currency.transferFrom(_user, address(escrow), transferAmount), "CentrifugeConnector/currency-transfer-failed"); } - gateway.increaseInvestOrder(cTranche.poolId, cTranche.trancheId, _user, currencyAddressToId[tranche.asset()], uint128(_currencyAmount)); + gateway.increaseInvestOrder(lPool.poolId(), lPool.trancheId(), _user, currencyAddressToId[lPool.asset()], currencyAmount); } function transfer(address currencyAddress, bytes32 recipient, uint128 amount) public { @@ -351,10 +346,11 @@ contract CentrifugeConnector 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 lPool = LiquidityPoolLike(liquidityPools[poolId][trancheId]); + LiquidityPoolLike lPool = LiquidityPoolLike(liquidityPools[poolId][trancheId][currency]); require(address(lPool) != address(0), "CentrifugeConnector/unknown-token"); require(lPool.balanceOf(msg.sender) >= amount, "CentrifugeConnector/insufficient-balance"); @@ -366,33 +362,34 @@ contract CentrifugeConnector is Auth { function transferTrancheTokensToEVM( uint64 poolId, bytes16 trancheId, + address currency, uint64 destinationChainId, address destinationAddress, uint128 amount ) public { - LiquidityPoolLike tranche = LiquidityPoolLike(liquidityPools[poolId][trancheId]); - require(address(tranche) != address(0), "CentrifugeConnector/unknown-token"); + LiquidityPoolLike lPool = LiquidityPoolLike(liquidityPools[poolId][trancheId][currency]); + require(address(lPool) != address(0), "CentrifugeConnector/unknown-token"); - require(tranche.balanceOf(msg.sender) >= amount, "CentrifugeConnector/insufficient-balance"); - tranche.burn(msg.sender, amount); + require(lPool.balanceOf(msg.sender) >= amount, "CentrifugeConnector/insufficient-balance"); + lPool.burn(msg.sender, amount); gateway.transferTrancheTokensToEVM( poolId, trancheId, msg.sender, destinationChainId, destinationAddress, amount ); } - function collectInvest(uint64 _poolId, bytes16 _trancheId) public { - LiquidityPoolLike tranche = LiquidityPoolLike(liquidityPools[_poolId][_trancheId]); - require(address(tranche) != address(0), "CentrifugeConnector/unknown-tranche-token"); - require(tranche.hasMember(msg.sender), "CentrifugeConnector/not-a-member"); + function collectInvest(uint64 _poolId, bytes16 _trancheId, address _currency) public { + LiquidityPoolLike lPool = LiquidityPoolLike(liquidityPools[_poolId][_trancheId][_currency]); + require(address(lPool) != address(0), "CentrifugeConnector/unknown-liquidity-pool"); + require(lPool.hasMember(msg.sender), "CentrifugeConnector/not-a-member"); gateway.collectInvest(_poolId, _trancheId, address(msg.sender)); } - function collectRedeem(uint64 _poolId, bytes16 _trancheId) public { - LiquidityPoolLike tranche = LiquidityPoolLike(liquidityPools[_poolId][_trancheId]); - require(address(tranche) != address(0), "CentrifugeConnector/unknown-tranche-token"); - require(tranche.hasMember(msg.sender), "CentrifugeConnector/not-a-member"); + function collectRedeem(uint64 _poolId, bytes16 _trancheId, address _currency) public { + LiquidityPoolLike lPool = LiquidityPoolLike(liquidityPools[_poolId][_trancheId][_currency]); + require(address(lPool) != address(0), "CentrifugeConnector/unknown-liquidity-pool"); + require(lPool.hasMember(msg.sender), "CentrifugeConnector/not-a-member"); gateway.collectRedeem(_poolId, _trancheId, address(msg.sender)); } @@ -449,19 +446,21 @@ contract CentrifugeConnector is Auth { emit TrancheAdded(_poolId, _trancheId); } - - function updateTokenPrice(uint64 _poolId, bytes16 _trancheId, uint128 _price) public onlyGateway { - address token = liquidityPools[_poolId][_trancheId]; - require(token != address(0), "CentrifugeConnector/invalid-pool-or-tranche"); - LiquidityPoolLike(token).updateTokenPrice(_price); - } - - function updateMember(uint64 _poolId, bytes16 _trancheId, address _user, uint64 _validUntil) public onlyGateway { - LiquidityPoolLike tranche = LiquidityPoolLike(liquidityPools[_poolId][_trancheId]); - require(address(tranche) != address(0), "CentrifugeConnector/invalid-pool-or-tranche"); - MemberlistLike memberlist = MemberlistLike(tranche.memberlist()); - memberlist.updateMember(_user, _validUntil); - } + + // TODO: updateTokenPrice for all the liquiditypools without providing currency + // function updateTokenPrice(uint64 _poolId, bytes16 _trancheId, uint128 _price) public onlyGateway { + // address lPool = liquidityPools[_poolId][_trancheId][_currency]; + // require(lPool != address(0), "CentrifugeConnector/invalid-pool-or-tranche"); + // LiquidityPoolLike(lPool).updateTokenPrice(_price); + // } + + // // TODO: updateTokenPrice for all the liquiditypools without providing currency + // function updateMember(uint64 _poolId, bytes16 _trancheId, address _user, uint64 _validUntil) public onlyGateway { + // LiquidityPoolLike tranche = LiquidityPoolLike(liquidityPools[_poolId][_trancheId]); + // require(address(tranche) != address(0), "CentrifugeConnector/invalid-pool-or-tranche"); + // MemberlistLike memberlist = MemberlistLike(tranche.memberlist()); + // memberlist.updateMember(_user, _validUntil); + // } function handleTransfer(uint128 currency, address recipient, uint128 amount) public onlyGateway { address currencyAddress = currencyIdToAddress[currency]; @@ -474,79 +473,81 @@ contract CentrifugeConnector is Auth { ); } - function handleTransferTrancheTokens(uint64 _poolId, bytes16 _trancheId, address _destinationAddress, uint128 _amount) - public - onlyGateway - { - LiquidityPoolLike tranche = LiquidityPoolLike(liquidityPools[_poolId][_trancheId]); - require(address(tranche) != address(0), "CentrifugeConnector/unknown-token"); + // function handleTransferTrancheTokens(uint64 _poolId, bytes16 _trancheId, address _destinationAddress, uint128 _amount) + // public + // onlyGateway + // { + // LiquidityPoolLike tranche = LiquidityPoolLike(liquidityPools[_poolId][_trancheId]); + // require(address(tranche) != address(0), "CentrifugeConnector/unknown-token"); - require(tranche.hasMember(_destinationAddress), "CentrifugeConnector/not-a-member"); - tranche.mint(_destinationAddress, _amount); - } + // require(tranche.hasMember(_destinationAddress), "CentrifugeConnector/not-a-member"); + // tranche.mint(_destinationAddress, _amount); + // } function handleDecreaseInvestOrder(uint64 _poolId, bytes16 _trancheId, address _user, uint128 _currency, uint128 _currencyPayout, uint128 _remainingInvestOrder) public onlyGateway { require(_currencyPayout != 0, "CentrifugeConnector/zero-payout"); - address currencyAddress = currencyIdToAddress[_currency]; - LiquidityPoolLike tranche = LiquidityPoolLike(liquidityPools[_poolId][_trancheId]); - require(address(tranche) != address(0), "CentrifugeConnector/tranche-does-not-exist"); - require(allowedPoolCurrencies[_poolId][currencyAddress], "CentrifugeConnector/pool-currency-not-allowed"); - require(currencyAddress != address(0), "CentrifugeConnector/unknown-currency"); - require(currencyAddress == tranche.asset(), "CentrifugeConnector/not-tranche-currency"); + address currency = currencyIdToAddress[_currency]; + LiquidityPoolLike lPool = LiquidityPoolLike(liquidityPools[_poolId][_trancheId][currency]); + require(address(lPool) != address(0), "CentrifugeConnector/tranche-does-not-exist"); + require(allowedPoolCurrencies[_poolId][currency], "CentrifugeConnector/pool-currency-not-allowed"); + require(currency != address(0), "CentrifugeConnector/unknown-currency"); + require(currency == lPool.asset(), "CentrifugeConnector/not-tranche-currency"); // TODO: escrow should give max approval on deployment - EscrowLike(escrow).approve(currencyAddress, address(this), _currencyPayout); + EscrowLike(escrow).approve(currency, address(this), _currencyPayout); require( - ERC20Like(currencyAddress).transferFrom(address(escrow), _user, _currencyPayout), + ERC20Like(currency).transferFrom(address(escrow), _user, _currencyPayout), "CentrifugeConnector/currency-transfer-failed" ); - orderbook[_user][address(tranche)].openInvest = _remainingInvestOrder; + orderbook[_user][address(lPool)].openInvest = _remainingInvestOrder; } function handleDecreaseRedeemOrder(uint64 _poolId, bytes16 _trancheId, address _user, uint128 _currency, uint128 _tokensPayout, uint128 _remainingRedeemOrder) public onlyGateway { require(_tokensPayout != 0, "CentrifugeConnector/zero-payout"); - LiquidityPoolLike tranche = LiquidityPoolLike(liquidityPools[_poolId][_trancheId]); - require(address(tranche) != address(0), "CentrifugeConnector/tranche-does-not-exist"); + address currency = currencyIdToAddress[_currency]; + LiquidityPoolLike lPool = LiquidityPoolLike(liquidityPools[_poolId][_trancheId][currency]); + require(address(lPool) != address(0), "CentrifugeConnector/tranche-does-not-exist"); - require(LiquidityPoolLike(tranche).hasMember(_user), "CentrifugeConnector/not-a-member"); + require(LiquidityPoolLike(lPool).hasMember(_user), "CentrifugeConnector/not-a-member"); // TODO: escrow should give max approval on deployment - EscrowLike(escrow).approve(address(tranche), address(this), _tokensPayout); + EscrowLike(escrow).approve(address(lPool), address(this), _tokensPayout); require( - tranche.transferFrom(address(escrow), _user, _tokensPayout), + lPool.transferFrom(address(escrow), _user, _tokensPayout), "CentrifugeConnector/trancheTokens-transfer-failed" ); - orderbook[_user][address(tranche)].openRedeem = _remainingRedeemOrder; + orderbook[_user][address(lPool)].openRedeem = _remainingRedeemOrder; } function handleCollectInvest(uint64 _poolId, bytes16 _trancheId, address _recepient, uint128 _currency, uint128 _currencyInvested, uint128 _tokensPayout, uint128 _remainingInvestOrder) public onlyGateway { require(_currencyInvested != 0, "CentrifugeConnector/zero-invest"); - address tranche = liquidityPools[_poolId][_trancheId]; - require(tranche != address(0), "CentrifugeConnector/tranche-does-not-exist"); + address currency = currencyIdToAddress[_currency]; + address lPool = liquidityPools[_poolId][_trancheId][currency]; + require(lPool != address(0), "CentrifugeConnector/tranche-does-not-exist"); - LPValues memory values = orderbook[_recepient][tranche]; + LPValues memory values = orderbook[_recepient][lPool]; values.openInvest = _remainingInvestOrder; values.maxDeposit = values.maxDeposit + _currencyInvested; values.maxMint = values.maxMint + _tokensPayout; - LiquidityPoolLike(tranche).mint(address(escrow), _tokensPayout); // mint to escrow. Recepeint can claim by calling withdraw / redeem + LiquidityPoolLike(lPool).mint(address(escrow), _tokensPayout); // mint to escrow. Recepeint can claim by calling withdraw / redeem } function handleCollectRedeem(uint64 _poolId, bytes16 _trancheId, address _recepient, uint128 _currency, uint128 _currencyPayout, uint128 _trancheTokensRedeemed, uint128 _remainingRedeemOrder) public onlyGateway { require(_trancheTokensRedeemed != 0, "CentrifugeConnector/zero-redeem"); - address tranche = liquidityPools[_poolId][_trancheId]; - require(tranche != address(0), "CentrifugeConnector/tranche-does-not-exist"); + address currency = currencyIdToAddress[_currency]; + address lPool = liquidityPools[_poolId][_trancheId][currency]; + require(lPool != address(0), "CentrifugeConnector/tranche-does-not-exist"); - LPValues memory values = orderbook[_recepient][tranche]; + LPValues memory values = orderbook[_recepient][lPool]; values.openRedeem = _remainingRedeemOrder; values.maxWithdraw = values.maxWithdraw + _currencyPayout; values.maxRedeem = values.maxRedeem + _trancheTokensRedeemed; - LiquidityPoolLike(tranche).burn(address(escrow), _trancheTokensRedeemed); // burned redeemed tokens from escrow + LiquidityPoolLike(lPool).burn(address(escrow), _trancheTokensRedeemed); // burned redeemed tokens from escrow } // ----- public functions - function deployLiquidityPool( uint64 _poolId, bytes16 _trancheId, @@ -561,11 +562,7 @@ contract CentrifugeConnector is Auth { uint128 currencyId = currencyAddressToId[_currency]; // gateway admin on liquidityPool liquidityPool = liquidityPoolFactory.newLiquidityPool(_poolId, _trancheId, currencyId, _currency, address(this), address(gateway), tranche.tokenName, tranche.tokenSymbol, tranche.decimals); - liquidityPools[_poolId][_trancheId][_currency] = liquidityPool; - LiquidityPoolInfo storage lPoolInfo = addressToLiquidityPoolInfo[liquidityPool]; - lPoolInfo.poolId = _poolId; - lPoolInfo.trancheId = _trancheId; address memberlist = memberlistFactory.newMemberlist(address(gateway)); // gateway admin on memberlist LiquidityPoolLike(liquidityPool).file("memberlist", memberlist); @@ -591,10 +588,10 @@ contract CentrifugeConnector is Auth { return true; } - function _trancheTokenCheck(uint64 _poolId, bytes16 _trancheId, address _user) internal returns (bool) { - LiquidityPoolLike tranche = LiquidityPoolLike(liquidityPools[_poolId][_trancheId]); - require(address(tranche) != address(0), "CentrifugeConnector/unknown-tranche-token"); - require(tranche.hasMember(_user), "CentrifugeConnector/not-a-member"); + function _trancheTokenCheck(uint64 _poolId, bytes16 _trancheId, address _currency, address _user) internal returns (bool) { + LiquidityPoolLike lPool = LiquidityPoolLike(liquidityPools[_poolId][_trancheId][_currency]); + require(address(lPool) != address(0), "CentrifugeConnector/unknown-tranche-token"); + require(lPool.hasMember(_user), "CentrifugeConnector/not-a-member"); return true; } diff --git a/src/liquidityPool/Factory.sol b/src/liquidityPool/Factory.sol index 15be44a4..8943f261 100644 --- a/src/liquidityPool/Factory.sol +++ b/src/liquidityPool/Factory.sol @@ -30,6 +30,7 @@ contract LiquidityPoolFactory { lPool.file("symbol", _symbol); lPool.file("connector", _connector); lPool.file("asset", _asset); + lPool.setPoolDetails(_poolId, _trancheId); lPool.deny(msg.sender); diff --git a/src/liquidityPool/LiquidityPool.sol b/src/liquidityPool/LiquidityPool.sol index 367d40b7..62f761c6 100644 --- a/src/liquidityPool/LiquidityPool.sol +++ b/src/liquidityPool/LiquidityPool.sol @@ -47,11 +47,14 @@ contract LiquidityPool is RestrictedToken { uint128 latestPrice; // lates share / token price uint256 lastPriceUpdate; // timestamp of the latest share / token price update + + // centrifuge chain pool and tranche details + uint64 public poolId; + bytes16 public trancheId; // events event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares); event Withdraw(address indexed sender, address indexed receiver, address indexed owner, uint256 assets, uint256 shares); - event File(bytes32 indexed what, address indexed data); constructor(uint8 _decimals) RestrictedToken(_decimals) {} @@ -64,6 +67,13 @@ contract LiquidityPool is RestrictedToken { emit File(_what, _data); } + /// @dev Centrifuge chain pool information to be files by factory on deployment + function setPoolDetails(uint64 _poolId, bytes16 _trancheId) public auth { + require(poolId == 0, "LiquidityPool/pool-details-already-set"); + poolId = _poolId; + trancheId = _trancheId; + } + /// @dev The total amount of vault shares /// @return Total amount of the underlying vault assets including accrued interest function totalAssets() public view returns (uint256) { diff --git a/src/token/restricted.sol b/src/token/restricted.sol index b208b24e..af48c548 100644 --- a/src/token/restricted.sol +++ b/src/token/restricted.sol @@ -41,7 +41,7 @@ contract RestrictedToken is ERC20 { } // --- Administration --- - function file(bytes32 what, address data) external auth { + function file(bytes32 what, address data) external virtual auth { if (what == "memberlist") memberlist = MemberlistLike(data); else revert("file-unrecognized-param"); emit File(what, data); From 93cb56c9f8468e044a705142f512a0acce4137ae Mon Sep 17 00:00:00 2001 From: ilinzweilin Date: Mon, 26 Jun 2023 14:59:41 +0100 Subject: [PATCH 15/24] add multi tranche support --- src/Connector.sol | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/src/Connector.sol b/src/Connector.sol index 01e577cf..7d3ed40d 100644 --- a/src/Connector.sol +++ b/src/Connector.sol @@ -75,6 +75,7 @@ struct Tranche { string tokenName; string tokenSymbol; uint8 decimals; + address[] liquidityPools; } /// @dev storing liquidity pool orders and deposit/redemption limits for a user @@ -447,20 +448,27 @@ contract CentrifugeConnector is Auth { emit TrancheAdded(_poolId, _trancheId); } - // TODO: updateTokenPrice for all the liquiditypools without providing currency - // function updateTokenPrice(uint64 _poolId, bytes16 _trancheId, uint128 _price) public onlyGateway { - // address lPool = liquidityPools[_poolId][_trancheId][_currency]; - // require(lPool != address(0), "CentrifugeConnector/invalid-pool-or-tranche"); - // LiquidityPoolLike(lPool).updateTokenPrice(_price); - // } + function updateTokenPrice(uint64 _poolId, bytes16 _trancheId, uint128 _price) public onlyGateway { + Tranche storage tranche = tranches[_poolId][_trancheId]; + require(tranche.createdAt > 0, "CentrifugeConnector/invalid-pool-or-tranche"); + for (uint i=0; i 0, "CentrifugeConnector/invalid-pool-or-tranche"); + for (uint i=0; i Date: Wed, 28 Jun 2023 00:28:18 +0200 Subject: [PATCH 16/24] format connectors file --- src/Connector.sol | 238 +++++++++++++++++++++++----------------------- 1 file changed, 119 insertions(+), 119 deletions(-) diff --git a/src/Connector.sol b/src/Connector.sol index 6fcf11a3..8c853e16 100644 --- a/src/Connector.sol +++ b/src/Connector.sol @@ -106,7 +106,7 @@ contract CentrifugeConnector is Auth { GatewayLike public gateway; EscrowLike public immutable escrow; - LiquidityPoolFactoryLike public immutable liquidityPoolFactory; //TODO use for LPPool deployment + LiquidityPoolFactoryLike public immutable liquidityPoolFactory; MemberlistFactoryLike public immutable memberlistFactory; // --- Events --- @@ -171,122 +171,6 @@ contract CentrifugeConnector is Auth { pool.isActive = _isActive; } - // --- Liquidity Pool Function --- - /// @dev calculates the avg share price for the deposited assets of a specific user - /// @dev - /// @return currencyAmount is type of uin256 to support the EIP4626 Liquidity Pool interface - function maxDeposit(address _user, address _liquidityPool) public view returns (uint256 currencyAmount) { - currencyAmount = uint256(orderbook[_user][_liquidityPool].maxDeposit); - } - - /// @dev - /// @return trancheTokenAmount type of uin256 to support the EIP4626 Liquidity Pool interface - function maxMint(address _user, address _liquidityPool) public view returns (uint256 trancheTokenAmount) { - trancheTokenAmount = uint256(orderbook[_user][_liquidityPool].maxMint); - } - - /// @dev - /// @return currencyAmount type of uin256 to support the EIP4626 Liquidity Pool interface - function maxWithdraw(address _user, address _liquidityPool) public view returns (uint256 currencyAmount) { - currencyAmount = uint256(orderbook[_user][_liquidityPool].maxWithdraw); - } - - /// @dev - /// @return trancheTokenAmount type of uin256 to support the EIP4626 Liquidity Pool interface - function maxRedeem(address _user, address _liquidityPool) public view returns (uint256 trancheTokenAmount) { - trancheTokenAmount = uint256(orderbook[_user][_liquidityPool].maxRedeem); - } - - /// @dev processes user's currency deposit / investment after the epoch has been executed on Centrifuge chain. - /// In case user's invest order was fullfilled on Centrifuge chain during epoch execution MaxDeposit and MaxMint are increased and trancheTokens can be transferred to user's wallet on calling processDeposit. - /// Note: The currency required to fullfill the invest order is already locked in escrow upon calling requestDeposit. - /// @notice trancheTokenAmount return value is type of uint256 to be compliant with EIP4626 LiquidityPool interface - /// @return trancheTokenAmount the amount of trancheTokens transferred to the user's wallet after successful depoit - function processDeposit(address _user, uint256 _currencyAmount) public onlyLiquidityPoolWard connectorActive poolActive auth returns (uint256) { - address _liquidityPool = msg.sender; - uint128 currencyAmount = _toUint128(_currencyAmount); - LiquidityPoolLike lPool = LiquidityPoolLike(_liquidityPool); - - require((currencyAmount <= orderbook[_user][_liquidityPool].maxDeposit), "CentrifugeConnector/amount-exceeds-deposit-limits"); - uint128 userTrancheTokenPriceLP = calcCustomTrancheTokenPrice( _user, _liquidityPool); - require((userTrancheTokenPriceLP > 0), "LiquidityPool/amount-exceeds-deposit-limits"); - uint128 trancheTokenAmount = currencyAmount / userTrancheTokenPriceLP; - - _decreaseDepositLimits(_user, _liquidityPool, currencyAmount, trancheTokenAmount); // decrease user's deposit limits for this lp - require(lPool.hasMember( _user), "CentrifugeConnector/trancheTokens-not-a-member"); - require(lPool.transferFrom(address(escrow), _user, trancheTokenAmount), "CentrifugeConnector/trancheTokens-transfer-failed"); - - emit DepositProcessed(_liquidityPool, _user, currencyAmount); - return uint256(trancheTokenAmount); - } - - /// @dev processes user's currency deposit / investment after the epoch has been executed on Centrifuge chain. - /// In case user's invest order was fullfilled on Centrifuge chain during epoch execution MaxDeposit and MaxMint are increased and trancheTokens can be transferred to user's wallet on calling processDeposit or processMint. - /// Note: The currency amount required to fullfill the invest order is already locked in escrow upon calling requestDeposit. - /// Note: The tranche tokens are already minted on collectInvest and are deposited to the escrow account until the users calls mint, or deposit. - /// @notice currencyAmount return value is type of uint256 to be compliant with EIP4626 LiquidityPool interface - /// @return currencyAmount the amount of liquidityPool assets invested and locked in escrow in order for the amount of tranche received after successful investment into the pool. - function processMint(address _user, uint256 _trancheTokenAmount) public onlyLiquidityPoolWard poolActive connectorActive auth returns (uint256) { - address _liquidityPool = msg.sender; - uint128 trancheTokenAmount = _toUint128(_trancheTokenAmount); - LiquidityPoolLike lPool = LiquidityPoolLike(_liquidityPool); - - require((trancheTokenAmount <= orderbook[_user][ _liquidityPool].maxMint), "CentrifugeConnector/amount-exceeds-mint-limits"); - uint128 userTrancheTokenPriceLP = calcCustomTrancheTokenPrice(_user, _liquidityPool); - require((userTrancheTokenPriceLP > 0), "LiquidityPool/amount-exceeds-mint-limits"); - uint128 currencyAmount = trancheTokenAmount * userTrancheTokenPriceLP; - - _decreaseDepositLimits(_user, _liquidityPool, currencyAmount, trancheTokenAmount); // decrease the possible deposit limits - require(lPool.hasMember( _user), "CentrifugeConnector/trancheTokens-not-a-member"); - require(lPool.transferFrom(address(escrow), _user, trancheTokenAmount), "CentrifugeConnector/trancheTokens-transfer-failed"); - - emit DepositProcessed(_liquidityPool, _user, currencyAmount); - return uint256(currencyAmount); - } - - /// @dev processes user's trancheToken redemption after the epoch has been executed on Centrifuge chain. - /// In case user's redempion order was fullfilled on Centrifuge chain during epoch execution MaxRedeem and MaxWithdraw are increased and LiquidityPool currency can be transferred to user's wallet on calling processRedeem or processWithdraw. - /// Note: The trancheToken amount required to fullfill the redemption order was already locked in escrow upon calling requestRedeem and burned upon collectRedeem. - /// @notice currencyAmount return value is type of uint256 to be compliant with EIP4626 LiquidityPool interface - /// @return currencyAmount the amount of liquidityPool assets received for the amount of redeemed/burned trancheTokens. - function processRedeem(uint256 _trancheTokenAmount, address _receiver, address _user) public onlyLiquidityPoolWard poolActive connectorActive auth returns (uint256) { - address _liquidityPool = msg.sender; - uint128 trancheTokenAmount = _toUint128(_trancheTokenAmount); - LiquidityPoolLike lPool = LiquidityPoolLike(_liquidityPool); - - require((trancheTokenAmount <= orderbook[_user][ _liquidityPool].maxRedeem), "CentrifugeConnector/amount-exceeds-redeem-limits"); - uint128 userTrancheTokenPriceLP = calcCustomTrancheTokenPrice(_user, _liquidityPool); - require((userTrancheTokenPriceLP > 0), "LiquidityPool/amount-exceeds-redemption-limits"); - uint128 currencyAmount = trancheTokenAmount * userTrancheTokenPriceLP; - - _decreaseRedemptionLimits(_user, _liquidityPool, currencyAmount, trancheTokenAmount); // decrease the possible deposit limits - require(ERC20Like(lPool.asset()).transferFrom(address(escrow), _receiver, currencyAmount), "CentrifugeConnector/shares-transfer-failed"); - - emit RedemptionProcessed(_liquidityPool, _user, trancheTokenAmount); - return uint256(currencyAmount); - } - - /// @dev processes user's trancheToken redemption after the epoch has been executed on Centrifuge chain. - /// In case user's redempion order was fullfilled on Centrifuge chain during epoch execution MaxRedeem and MaxWithdraw are increased and LiquidityPool currency can be transferred to user's wallet on calling processRedeem or processWithdraw. - /// Note: The trancheToken amount required to fullfill the redemption order was already locked in escrow upon calling requestRedeem and burned upon collectRedeem. - /// @notice trancheTokenAmount return value is type of uint256 to be compliant with EIP4626 LiquidityPool interface - /// @return trancheTokenAmount the amount of trancheTokens redeemed/burned required to receive the currencyAmount payout/withdrawel. - function processWithdraw(uint256 _currencyAmount, address _receiver, address _user) public onlyLiquidityPoolWard poolActive connectorActive auth returns (uint256) { - address _liquidityPool = msg.sender; - uint128 currencyAmount = _toUint128(_currencyAmount); - LiquidityPoolLike lPool = LiquidityPoolLike(_liquidityPool); - - require((currencyAmount <= orderbook[_user][ _liquidityPool].maxWithdraw), "CentrifugeConnector/amount-exceeds-withdraw-limits"); - uint128 userTrancheTokenPriceLP = calcCustomTrancheTokenPrice(_user, _liquidityPool); - require((userTrancheTokenPriceLP > 0), "LiquidityPool/amount-exceeds-withdraw-limits"); - uint128 trancheTokenAmount = currencyAmount / userTrancheTokenPriceLP; - - _decreaseRedemptionLimits(_user, _liquidityPool, currencyAmount, trancheTokenAmount); - require(ERC20Like(lPool.asset()).transferFrom(address(escrow), _receiver, currencyAmount), "CentrifugeConnector/trancheTokens-transfer-failed"); - return uint256(trancheTokenAmount); - } - - // --- liquidity pool outgoing message handling --- function requestRedeem(uint256 _trancheTokensAmount, address _user) onlyLiquidityPoolWard poolActive connectorActive public auth { @@ -353,7 +237,7 @@ contract CentrifugeConnector is Auth { gateway.increaseInvestOrder(lPool.poolId(), lPool.trancheId(), _user, currencyAddressToId[lPool.asset()], currencyAmount); } - // --- public Ougoing message handling --- + // --- public outgoing message handling --- function collectInvest(uint64 _poolId, bytes16 _trancheId, address _currency) public { LiquidityPoolLike lPool = LiquidityPoolLike(liquidityPools[_poolId][_trancheId][_currency]); @@ -593,7 +477,123 @@ contract CentrifugeConnector is Auth { liquidityPool.mint(_destinationAddress, _amount); } - + // ----- liquidity pool functions + + // --- Liquidity Pool Function --- + /// @dev calculates the avg share price for the deposited assets of a specific user + /// @return currencyAmount is type of uin256 to support the EIP4626 Liquidity Pool interface + function maxDeposit(address _user, address _liquidityPool) public view returns (uint256 currencyAmount) { + currencyAmount = uint256(orderbook[_user][_liquidityPool].maxDeposit); + } + + /// @dev + /// @return trancheTokenAmount type of uin256 to support the EIP4626 Liquidity Pool interface + function maxMint(address _user, address _liquidityPool) public view returns (uint256 trancheTokenAmount) { + trancheTokenAmount = uint256(orderbook[_user][_liquidityPool].maxMint); + } + + /// @dev + /// @return currencyAmount type of uin256 to support the EIP4626 Liquidity Pool interface + function maxWithdraw(address _user, address _liquidityPool) public view returns (uint256 currencyAmount) { + currencyAmount = uint256(orderbook[_user][_liquidityPool].maxWithdraw); + } + + /// @dev + /// @return trancheTokenAmount type of uin256 to support the EIP4626 Liquidity Pool interface + function maxRedeem(address _user, address _liquidityPool) public view returns (uint256 trancheTokenAmount) { + trancheTokenAmount = uint256(orderbook[_user][_liquidityPool].maxRedeem); + } + + /// @dev processes user's currency deposit / investment after the epoch has been executed on Centrifuge chain. + /// In case user's invest order was fullfilled on Centrifuge chain during epoch execution MaxDeposit and MaxMint are increased and trancheTokens can be transferred to user's wallet on calling processDeposit. + /// Note: The currency required to fullfill the invest order is already locked in escrow upon calling requestDeposit. + /// @notice trancheTokenAmount return value is type of uint256 to be compliant with EIP4626 LiquidityPool interface + /// @return trancheTokenAmount the amount of trancheTokens transferred to the user's wallet after successful depoit + function processDeposit(address _user, uint256 _currencyAmount) public onlyLiquidityPoolWard connectorActive poolActive auth returns (uint256) { + address _liquidityPool = msg.sender; + uint128 currencyAmount = _toUint128(_currencyAmount); + LiquidityPoolLike lPool = LiquidityPoolLike(_liquidityPool); + + require((currencyAmount <= orderbook[_user][_liquidityPool].maxDeposit), "CentrifugeConnector/amount-exceeds-deposit-limits"); + uint128 userTrancheTokenPriceLP = calcCustomTrancheTokenPrice( _user, _liquidityPool); + require((userTrancheTokenPriceLP > 0), "LiquidityPool/amount-exceeds-deposit-limits"); + uint128 trancheTokenAmount = currencyAmount / userTrancheTokenPriceLP; + + _decreaseDepositLimits(_user, _liquidityPool, currencyAmount, trancheTokenAmount); // decrease user's deposit limits for this lp + require(lPool.hasMember( _user), "CentrifugeConnector/trancheTokens-not-a-member"); + require(lPool.transferFrom(address(escrow), _user, trancheTokenAmount), "CentrifugeConnector/trancheTokens-transfer-failed"); + + emit DepositProcessed(_liquidityPool, _user, currencyAmount); + return uint256(trancheTokenAmount); + } + + /// @dev processes user's currency deposit / investment after the epoch has been executed on Centrifuge chain. + /// In case user's invest order was fullfilled on Centrifuge chain during epoch execution MaxDeposit and MaxMint are increased and trancheTokens can be transferred to user's wallet on calling processDeposit or processMint. + /// Note: The currency amount required to fullfill the invest order is already locked in escrow upon calling requestDeposit. + /// Note: The tranche tokens are already minted on collectInvest and are deposited to the escrow account until the users calls mint, or deposit. + /// @notice currencyAmount return value is type of uint256 to be compliant with EIP4626 LiquidityPool interface + /// @return currencyAmount the amount of liquidityPool assets invested and locked in escrow in order for the amount of tranche received after successful investment into the pool. + function processMint(address _user, uint256 _trancheTokenAmount) public onlyLiquidityPoolWard poolActive connectorActive auth returns (uint256) { + address _liquidityPool = msg.sender; + uint128 trancheTokenAmount = _toUint128(_trancheTokenAmount); + LiquidityPoolLike lPool = LiquidityPoolLike(_liquidityPool); + + require((trancheTokenAmount <= orderbook[_user][ _liquidityPool].maxMint), "CentrifugeConnector/amount-exceeds-mint-limits"); + uint128 userTrancheTokenPriceLP = calcCustomTrancheTokenPrice(_user, _liquidityPool); + require((userTrancheTokenPriceLP > 0), "LiquidityPool/amount-exceeds-mint-limits"); + uint128 currencyAmount = trancheTokenAmount * userTrancheTokenPriceLP; + + _decreaseDepositLimits(_user, _liquidityPool, currencyAmount, trancheTokenAmount); // decrease the possible deposit limits + require(lPool.hasMember( _user), "CentrifugeConnector/trancheTokens-not-a-member"); + require(lPool.transferFrom(address(escrow), _user, trancheTokenAmount), "CentrifugeConnector/trancheTokens-transfer-failed"); + + emit DepositProcessed(_liquidityPool, _user, currencyAmount); + return uint256(currencyAmount); + } + + /// @dev processes user's trancheToken redemption after the epoch has been executed on Centrifuge chain. + /// In case user's redempion order was fullfilled on Centrifuge chain during epoch execution MaxRedeem and MaxWithdraw are increased and LiquidityPool currency can be transferred to user's wallet on calling processRedeem or processWithdraw. + /// Note: The trancheToken amount required to fullfill the redemption order was already locked in escrow upon calling requestRedeem and burned upon collectRedeem. + /// @notice currencyAmount return value is type of uint256 to be compliant with EIP4626 LiquidityPool interface + /// @return currencyAmount the amount of liquidityPool assets received for the amount of redeemed/burned trancheTokens. + function processRedeem(uint256 _trancheTokenAmount, address _receiver, address _user) public onlyLiquidityPoolWard poolActive connectorActive auth returns (uint256) { + address _liquidityPool = msg.sender; + uint128 trancheTokenAmount = _toUint128(_trancheTokenAmount); + LiquidityPoolLike lPool = LiquidityPoolLike(_liquidityPool); + + require((trancheTokenAmount <= orderbook[_user][ _liquidityPool].maxRedeem), "CentrifugeConnector/amount-exceeds-redeem-limits"); + uint128 userTrancheTokenPriceLP = calcCustomTrancheTokenPrice(_user, _liquidityPool); + require((userTrancheTokenPriceLP > 0), "LiquidityPool/amount-exceeds-redemption-limits"); + uint128 currencyAmount = trancheTokenAmount * userTrancheTokenPriceLP; + + _decreaseRedemptionLimits(_user, _liquidityPool, currencyAmount, trancheTokenAmount); // decrease the possible deposit limits + require(ERC20Like(lPool.asset()).transferFrom(address(escrow), _receiver, currencyAmount), "CentrifugeConnector/shares-transfer-failed"); + + emit RedemptionProcessed(_liquidityPool, _user, trancheTokenAmount); + return uint256(currencyAmount); + } + + /// @dev processes user's trancheToken redemption after the epoch has been executed on Centrifuge chain. + /// In case user's redempion order was fullfilled on Centrifuge chain during epoch execution MaxRedeem and MaxWithdraw are increased and LiquidityPool currency can be transferred to user's wallet on calling processRedeem or processWithdraw. + /// Note: The trancheToken amount required to fullfill the redemption order was already locked in escrow upon calling requestRedeem and burned upon collectRedeem. + /// @notice trancheTokenAmount return value is type of uint256 to be compliant with EIP4626 LiquidityPool interface + /// @return trancheTokenAmount the amount of trancheTokens redeemed/burned required to receive the currencyAmount payout/withdrawel. + function processWithdraw(uint256 _currencyAmount, address _receiver, address _user) public onlyLiquidityPoolWard poolActive connectorActive auth returns (uint256) { + address _liquidityPool = msg.sender; + uint128 currencyAmount = _toUint128(_currencyAmount); + LiquidityPoolLike lPool = LiquidityPoolLike(_liquidityPool); + + require((currencyAmount <= orderbook[_user][ _liquidityPool].maxWithdraw), "CentrifugeConnector/amount-exceeds-withdraw-limits"); + uint128 userTrancheTokenPriceLP = calcCustomTrancheTokenPrice(_user, _liquidityPool); + require((userTrancheTokenPriceLP > 0), "LiquidityPool/amount-exceeds-withdraw-limits"); + uint128 trancheTokenAmount = currencyAmount / userTrancheTokenPriceLP; + + _decreaseRedemptionLimits(_user, _liquidityPool, currencyAmount, trancheTokenAmount); + require(ERC20Like(lPool.asset()).transferFrom(address(escrow), _receiver, currencyAmount), "CentrifugeConnector/trancheTokens-transfer-failed"); + return uint256(trancheTokenAmount); + } + + // ----- public functions function deployLiquidityPool( uint64 _poolId, From 985d1934b4777ca3bfff28baa95eed745677720b Mon Sep 17 00:00:00 2001 From: ilinzweilin Date: Wed, 28 Jun 2023 09:33:59 +0200 Subject: [PATCH 17/24] add global escrow limits and more docs --- script/Connector-Axelar.s.sol | 3 - src/Connector.sol | 107 ++++++++++++++++++++-------------- 2 files changed, 63 insertions(+), 47 deletions(-) diff --git a/script/Connector-Axelar.s.sol b/script/Connector-Axelar.s.sol index e70f98b0..01b227eb 100644 --- a/script/Connector-Axelar.s.sol +++ b/script/Connector-Axelar.s.sol @@ -47,9 +47,6 @@ contract ConnectorAxelarScript is Script { router.rely(address(gateway)); ConnectorEscrow(address(escrow_)).rely(address(gateway)); - // escrow give full permissions - - // TODO: rely pauseMultisig on pauseAdmin pauseAdmin.rely(address(0)); pauseAdmin.deny(address(this)); diff --git a/src/Connector.sol b/src/Connector.sol index 8c853e16..86ecab06 100644 --- a/src/Connector.sol +++ b/src/Connector.sol @@ -60,25 +60,26 @@ interface EscrowLike { function approve(address token, address spender, uint256 value) external; } -/// @dev storing information about pools on Centrifuge chain +/// @dev centrifuge chain pool struct Pool { uint64 poolId; uint256 createdAt; bool isActive; } -/// @dev storing information about tranches on Centrifuge chain +/// @dev centrifuge chain tranche struct Tranche { uint64 poolId; bytes16 trancheId; uint256 createdAt; string tokenName; string tokenSymbol; + // important: the decimals of the leading pool currency. Liquidity Pool shares have to be denomatimated with the same precision. uint8 decimals; address[] liquidityPools; } -/// @dev storing liquidity pool orders and deposit/redemption limits for a user +/// @dev liquidity pool orders and deposit/redemption limits per user struct LPValues { uint128 maxDeposit; uint128 maxMint; @@ -90,25 +91,26 @@ struct LPValues { contract CentrifugeConnector is Auth { - mapping(uint64 => Pool) public pools; // pools on Centrifuge chain + mapping(uint64 => Pool) public pools; // pools on centrifuge chain + mapping(uint64 => mapping(bytes16 => Tranche)) public tranches; // centrifuge chain tranches + mapping(uint64 => mapping(bytes16 => mapping(address => address))) public liquidityPools; // evm liquidity pools - pool & tranche & currency -> liquidity pool address + mapping(address => uint) public liquidityPoolWards; // access permissions liquidity pool invest / redeem functions + mapping(address => mapping(address => LPValues)) public orderbook; // liquidity pool orders & limits per user - mapping(uint64 => mapping(bytes16 => mapping(address => address))) public liquidityPools; // Centrifuge chain tranche -> currency -> liquidity pool address // Todo: add currency - mapping(address => uint) public liquidityPoolWards; // liquidityPools that are allowed to call invest/redeem functions - mapping(uint64 => mapping(bytes16 => Tranche)) public tranches; // Centrifuge chain tranches - - // mapping(address => Tranche) public tranches; // liquidityPool -> Centrifuge chain tranches - mapping(address => mapping(address => LPValues)) public orderbook; // outstanding liquidity pool orders & limits per user & liquidity pool - - mapping(uint128 => address) public currencyIdToAddress; + mapping(uint128 => address) public currencyIdToAddress; // chain agnostic currency id -> evm currency address mapping(address => uint128) public currencyAddressToId; // The reverse mapping of `currencyIdToAddress` - mapping(uint64 => mapping(address => bool)) public allowedPoolCurrencies; + mapping(uint64 => mapping(address => bool)) public allowedPoolCurrencies; // supported currencies per pool GatewayLike public gateway; EscrowLike public immutable escrow; + // factories for liquidity pool deployments LiquidityPoolFactoryLike public immutable liquidityPoolFactory; MemberlistFactoryLike public immutable memberlistFactory; + uint256 MAX_UINT256 = 2**256 - 1; + uint256 MAX_UINT128 = 2**128 - 1; + // --- Events --- event File(bytes32 indexed what, address data); event CurrencyAdded(uint128 indexed currency, address indexed currencyAddress); @@ -130,8 +132,9 @@ contract CentrifugeConnector is Auth { } // --- Getters --- - function getLiquidityPoolsForTranche(uint64 _poolId, bytes16 _trancheId) public view returns (address[] memory liquidityPools) { - liquidityPools = tranches[_poolId][_trancheId].liquidityPools; + /// @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 checks whether a Centrifuge pool is active - can be used to prevent deposit / redemption requests to/from certain pools & avoid transfers from escrow related to inactive pools. @@ -147,13 +150,15 @@ contract CentrifugeConnector is Auth { _; } + /// @dev gateway must be message.sender. permissions check for incoming message handling. modifier onlyGateway() { require(msg.sender == address(gateway), "CentrifugeConnector/not-the-gateway"); _; } + /// @dev liquidity pool must be message.sender. permissions check for liquidity pool gated functions. modifier onlyLiquidityPoolWard() { - require(liquidityPoolWards[msg.sender] == 1, "CentrifugeConnector/not-liquidity-pool-ward"); + require(liquidityPoolWards[msg.sender] == 1, "CentrifugeConnector/not-liquidity-pool"); _; } @@ -173,55 +178,68 @@ contract CentrifugeConnector is Auth { // --- liquidity pool outgoing message handling --- - function requestRedeem(uint256 _trancheTokensAmount, address _user) onlyLiquidityPoolWard poolActive connectorActive public auth { + /// @dev request tranche token redemption. Liquidity pools have to request redemptions from the centrifuge chain before actual currency payouts can be done. + /// The redemption requests are added to the order book on centrifuge chain. Once the next epoch is executed on centrifuge chain, liquidity pools can proceed with currency payouts in case their orders got fullfilled. + /// @notice The user tranche tokens required to fullfill the redemption request have to be locked, even though the currency payout can only happen after epoch execution. + /// This function automatically closed all the outstading investment orders for the user. + function requestRedeem(uint256 _trancheTokenAmount, address _user) poolActive connectorActive public onlyLiquidityPoolWard { address _liquidityPool = msg.sender; LPValues storage lpValues = orderbook[_user][ _liquidityPool]; LiquidityPoolLike lPool = LiquidityPoolLike(_liquidityPool); - uint128 trancheTokensAmount = _toUint128(_trancheTokensAmount); + uint128 trancheTokenAmount = _toUint128(_trancheTokenAmount); - require(_poolCurrencyCheck(lPool.poolId(), lPool.asset()), "CentrifugeConnector/currency-not-supported"); + // check if liquidity pool currency is supported by the centrifuge pool + require(_poolCurrencyCheck(lPool.poolId(), lPool.asset()), "CentrifugeConnector/currency-not-supported"); + // check if user is allowed to hold the restriced liquidity pool tokens require(_trancheTokenCheck(lPool.poolId(), lPool.trancheId(), lPool.asset(), _user), "CentrifugeConnector/tranche-tokens-not-supported"); if (lpValues.openInvest > 0) { // cancel outstanding deposit orders - gateway.decreaseInvestOrder(lPool.poolId(), lPool.trancheId(), _user, currencyAddressToId[lPool.asset()], uint128(lpValues.openInvest)); + gateway.decreaseInvestOrder(lPool.poolId(), lPool.trancheId(), _user, currencyAddressToId[lPool.asset()], lpValues.openInvest); } - if(trancheTokensAmount == 0) { // case: user only wants to cancel outstanding orders + if(trancheTokenAmount == 0) { // case: outstanding deposit orders only needed to be cancelled return; } - if(lpValues.maxMint >= trancheTokensAmount) { // case: user has unclaimed trancheTokens in escrow -> more than redemption request - uint128 userTrancheTokenPrice = calcCustomTrancheTokenPrice( _user, _liquidityPool); - uint128 assets = trancheTokensAmount * userTrancheTokenPrice; - _decreaseDepositLimits(_user, _liquidityPool, assets, trancheTokensAmount); + if(lpValues.maxMint >= trancheTokenAmount) { // case: user has unclaimed trancheTokens in escrow -> more than redemption request + uint128 userTrancheTokenPrice = calcCustomTrancheTokenPrice(_user, _liquidityPool); + uint128 currencyAmount = trancheTokenAmount * userTrancheTokenPrice; + _decreaseDepositLimits(_user, _liquidityPool, currencyAmount, trancheTokenAmount); } else { - uint transferAmount = trancheTokensAmount - lpValues.maxMint; + uint transferAmount = trancheTokenAmount - lpValues.maxMint; lpValues.maxDeposit = 0; lpValues.maxMint = 0; - require(lPool.balanceOf(_user) >= trancheTokensAmount, "CentrifugeConnector/insufficient-tranche-token-balance"); + // transfer the differene between required and locked tranche tokens from user to escrow + require(lPool.balanceOf(_user) >= transferAmount, "CentrifugeConnector/insufficient-tranche-token-balance"); require(lPool.transferFrom(_user, address(escrow), transferAmount), "CentrifugeConnector/tranche-token-transfer-failed"); } - gateway.increaseRedeemOrder(lPool.poolId(), lPool.trancheId(), _user, currencyAddressToId[lPool.asset()], trancheTokensAmount); + gateway.increaseRedeemOrder(lPool.poolId(), lPool.trancheId(), _user, currencyAddressToId[lPool.asset()], trancheTokenAmount); } - function requestDeposit(uint _currencyAmount, address _user) onlyLiquidityPoolWard poolActive connectorActive public auth { + /// @dev request tranche token redemption. Liquidity pools have to request investments from the centrifuge chain before actual tranche token payouts can be done. + /// The deposit requests are added to the order book on centrifuge chain. Once the next epoch is executed on centrifuge chain, liquidity pools can proceed with tranche token payouts in case their orders got fullfilled. + /// @notice The user currency amount equired to fullfill the deposit request have to be locked, even though the tranche token payout can only happen after epoch execution. + /// This function automatically closed all the outstading redemption orders for the user. + function requestDeposit(uint _currencyAmount, address _user) poolActive connectorActive public onlyLiquidityPoolWard { address _liquidityPool = msg.sender; LPValues storage lpValues = orderbook[_user][ _liquidityPool]; LiquidityPoolLike lPool = LiquidityPoolLike(_liquidityPool); ERC20Like currency = ERC20Like(lPool.asset()); uint128 currencyAmount = _toUint128(_currencyAmount); + // check if liquidity pool currency is supported by the centrifuge pool require(_poolCurrencyCheck(lPool.poolId(), lPool.asset()), "CentrifugeConnector/currency-not-supported"); + // check if user is allowed to hold the restriced liquidity pool tokens require(_trancheTokenCheck(lPool.poolId(), lPool.trancheId(), lPool.asset(), _user), "CentrifugeConnector/tranche-tokens-not-supported"); if (lpValues.openRedeem > 0) { // cancel outstanding redeem orders - gateway.decreaseRedeemOrder(lPool.poolId(), lPool.trancheId(), _user, currencyAddressToId[lPool.asset()], uint128(lpValues.openRedeem)); + gateway.decreaseRedeemOrder(lPool.poolId(), lPool.trancheId(), _user, currencyAddressToId[lPool.asset()], lpValues.openRedeem); } - if(currencyAmount == 0) { // case: user only wants to cancel outstanding redemptions + if(currencyAmount == 0) { // case: outstanding redemption orders only needed to be cancelled return; } - if(lpValues.maxWithdraw >= currencyAmount) { // case: user has some claimable fund in escrow -> funds > Deposit request + if(lpValues.maxWithdraw >= currencyAmount) { // case: user has some claimable funds in escrow -> funds > depositRequest currencyAmount uint128 userTrancheTokenPrice = calcCustomTrancheTokenPrice( _user, _liquidityPool); uint128 trancheTokens = currencyAmount / userTrancheTokenPrice; _decreaseRedemptionLimits(_user, _liquidityPool, currencyAmount, trancheTokens); @@ -230,10 +248,10 @@ contract CentrifugeConnector is Auth { lpValues.maxWithdraw = 0; lpValues.maxRedeem = 0; + // transfer the differene between required and locked currency from user to escrow require(currency.balanceOf(_user) >= transferAmount, "CentrifugeConnector/insufficient-balance"); require(currency.transferFrom(_user, address(escrow), transferAmount), "CentrifugeConnector/currency-transfer-failed"); } - gateway.increaseInvestOrder(lPool.poolId(), lPool.trancheId(), _user, currencyAddressToId[lPool.asset()], currencyAmount); } @@ -313,6 +331,9 @@ contract CentrifugeConnector is Auth { currencyIdToAddress[currency] = currencyAddress; currencyAddressToId[currencyAddress] = currency; + + // enable connectors to take the currency out of escrow in case of redemptions + EscrowLike(escrow).approve(currencyAddress, address(this), MAX_UINT256); emit CurrencyAdded(currency, currencyAddress); } @@ -424,10 +445,6 @@ contract CentrifugeConnector is Auth { require(allowedPoolCurrencies[_poolId][currency], "CentrifugeConnector/pool-currency-not-allowed"); require(currency != address(0), "CentrifugeConnector/unknown-currency"); require(currency == lPool.asset(), "CentrifugeConnector/not-tranche-currency"); - - // TODO: escrow should give max approval on deployment - EscrowLike(escrow).approve(currency, address(this), _currencyPayout); - require( ERC20Like(currency).transferFrom(address(escrow), _user, _currencyPayout), "CentrifugeConnector/currency-transfer-failed" @@ -509,7 +526,7 @@ contract CentrifugeConnector is Auth { /// Note: The currency required to fullfill the invest order is already locked in escrow upon calling requestDeposit. /// @notice trancheTokenAmount return value is type of uint256 to be compliant with EIP4626 LiquidityPool interface /// @return trancheTokenAmount the amount of trancheTokens transferred to the user's wallet after successful depoit - function processDeposit(address _user, uint256 _currencyAmount) public onlyLiquidityPoolWard connectorActive poolActive auth returns (uint256) { + function processDeposit(address _user, uint256 _currencyAmount) connectorActive poolActive public onlyLiquidityPoolWard returns (uint256) { address _liquidityPool = msg.sender; uint128 currencyAmount = _toUint128(_currencyAmount); LiquidityPoolLike lPool = LiquidityPoolLike(_liquidityPool); @@ -533,7 +550,7 @@ contract CentrifugeConnector is Auth { /// Note: The tranche tokens are already minted on collectInvest and are deposited to the escrow account until the users calls mint, or deposit. /// @notice currencyAmount return value is type of uint256 to be compliant with EIP4626 LiquidityPool interface /// @return currencyAmount the amount of liquidityPool assets invested and locked in escrow in order for the amount of tranche received after successful investment into the pool. - function processMint(address _user, uint256 _trancheTokenAmount) public onlyLiquidityPoolWard poolActive connectorActive auth returns (uint256) { + function processMint(address _user, uint256 _trancheTokenAmount) poolActive connectorActive public onlyLiquidityPoolWard returns (uint256) { address _liquidityPool = msg.sender; uint128 trancheTokenAmount = _toUint128(_trancheTokenAmount); LiquidityPoolLike lPool = LiquidityPoolLike(_liquidityPool); @@ -556,7 +573,7 @@ contract CentrifugeConnector is Auth { /// Note: The trancheToken amount required to fullfill the redemption order was already locked in escrow upon calling requestRedeem and burned upon collectRedeem. /// @notice currencyAmount return value is type of uint256 to be compliant with EIP4626 LiquidityPool interface /// @return currencyAmount the amount of liquidityPool assets received for the amount of redeemed/burned trancheTokens. - function processRedeem(uint256 _trancheTokenAmount, address _receiver, address _user) public onlyLiquidityPoolWard poolActive connectorActive auth returns (uint256) { + function processRedeem(uint256 _trancheTokenAmount, address _receiver, address _user) poolActive connectorActive public onlyLiquidityPoolWard returns (uint256) { address _liquidityPool = msg.sender; uint128 trancheTokenAmount = _toUint128(_trancheTokenAmount); LiquidityPoolLike lPool = LiquidityPoolLike(_liquidityPool); @@ -578,7 +595,7 @@ contract CentrifugeConnector is Auth { /// Note: The trancheToken amount required to fullfill the redemption order was already locked in escrow upon calling requestRedeem and burned upon collectRedeem. /// @notice trancheTokenAmount return value is type of uint256 to be compliant with EIP4626 LiquidityPool interface /// @return trancheTokenAmount the amount of trancheTokens redeemed/burned required to receive the currencyAmount payout/withdrawel. - function processWithdraw(uint256 _currencyAmount, address _receiver, address _user) public onlyLiquidityPoolWard poolActive connectorActive auth returns (uint256) { + function processWithdraw(uint256 _currencyAmount, address _receiver, address _user) poolActive connectorActive public onlyLiquidityPoolWard returns (uint256) { address _liquidityPool = msg.sender; uint128 currencyAmount = _toUint128(_currencyAmount); LiquidityPoolLike lPool = LiquidityPoolLike(_liquidityPool); @@ -609,13 +626,15 @@ contract CentrifugeConnector is Auth { require(_poolCurrencyCheck(_poolId, _currency), "CentrifugeConnector/currency-not-supported"); // currency must be supported by pool uint128 currencyId = currencyAddressToId[_currency]; - // gateway admin on liquidityPool & memberlist + // 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(_poolId, _trancheId, currencyId, _currency, address(this), address(gateway), memberlist, tranche.tokenName, tranche.tokenSymbol, tranche.decimals); liquidityPools[_poolId][_trancheId][_currency] = liquidityPool; liquidityPoolWards[liquidityPool] = 1; // give liquidityPool permissions, so that invest & redeem functions can be called 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; @@ -674,8 +693,8 @@ contract CentrifugeConnector is Auth { /// @dev safe type conversion from uint256 to uint128. Revert if value is too big to be stored with uint128. Avoid data loss. /// @return value - safely converted without data loss - function _toUint128(uint256 _value) internal pure returns (uint128 value) { - if (_value > 2 ** 128) { + function _toUint128(uint256 _value) internal view returns (uint128 value) { + if (_value > MAX_UINT128) { revert(); } else { value = uint128(_value); From 2e12724a471dbf6a219c9e17c13f0f91c638cc78 Mon Sep 17 00:00:00 2001 From: ilinzweilin Date: Wed, 28 Jun 2023 10:20:05 +0200 Subject: [PATCH 18/24] add global escrow limits and more docs --- src/Connector.sol | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Connector.sol b/src/Connector.sol index 86ecab06..d11a53bd 100644 --- a/src/Connector.sol +++ b/src/Connector.sol @@ -213,7 +213,6 @@ contract CentrifugeConnector is Auth { require(lPool.balanceOf(_user) >= transferAmount, "CentrifugeConnector/insufficient-tranche-token-balance"); require(lPool.transferFrom(_user, address(escrow), transferAmount), "CentrifugeConnector/tranche-token-transfer-failed"); } - gateway.increaseRedeemOrder(lPool.poolId(), lPool.trancheId(), _user, currencyAddressToId[lPool.asset()], trancheTokenAmount); } @@ -497,25 +496,22 @@ contract CentrifugeConnector is Auth { // ----- liquidity pool functions // --- Liquidity Pool Function --- - /// @dev calculates the avg share price for the deposited assets of a specific user + /// @return currencyAmount is type of uin256 to support the EIP4626 Liquidity Pool interface function maxDeposit(address _user, address _liquidityPool) public view returns (uint256 currencyAmount) { currencyAmount = uint256(orderbook[_user][_liquidityPool].maxDeposit); } - /// @dev /// @return trancheTokenAmount type of uin256 to support the EIP4626 Liquidity Pool interface function maxMint(address _user, address _liquidityPool) public view returns (uint256 trancheTokenAmount) { trancheTokenAmount = uint256(orderbook[_user][_liquidityPool].maxMint); } - /// @dev /// @return currencyAmount type of uin256 to support the EIP4626 Liquidity Pool interface function maxWithdraw(address _user, address _liquidityPool) public view returns (uint256 currencyAmount) { currencyAmount = uint256(orderbook[_user][_liquidityPool].maxWithdraw); } - /// @dev /// @return trancheTokenAmount type of uin256 to support the EIP4626 Liquidity Pool interface function maxRedeem(address _user, address _liquidityPool) public view returns (uint256 trancheTokenAmount) { trancheTokenAmount = uint256(orderbook[_user][_liquidityPool].maxRedeem); From 728d04fb3fa4495ff8b1151feb3dd6d7fc802cf6 Mon Sep 17 00:00:00 2001 From: ilinzweilin Date: Wed, 28 Jun 2023 10:47:44 +0200 Subject: [PATCH 19/24] fix fix incoming message interfaces --- src/Connector.sol | 62 ++++++----------------------------------------- 1 file changed, 8 insertions(+), 54 deletions(-) diff --git a/src/Connector.sol b/src/Connector.sol index 36061bf6..34835828 100644 --- a/src/Connector.sol +++ b/src/Connector.sol @@ -191,7 +191,7 @@ contract CentrifugeConnector is Auth { // check if liquidity pool currency is supported by the centrifuge pool require(_poolCurrencyCheck(lPool.poolId(), lPool.asset()), "CentrifugeConnector/currency-not-supported"); // check if user is allowed to hold the restriced liquidity pool tokens - require(_trancheTokenCheck(lPool.poolId(), lPool.trancheId(), lPool.asset(), _user), "CentrifugeConnector/tranche-tokens-not-supported"); + require(_liquidityPoolTokensCheck(lPool.poolId(), lPool.trancheId(), lPool.asset(), _user), "CentrifugeConnector/tranche-tokens-not-supported"); if (lpValues.openInvest > 0) { // cancel outstanding deposit orders gateway.decreaseInvestOrder(lPool.poolId(), lPool.trancheId(), _user, currencyAddressToId[lPool.asset()], lpValues.openInvest); @@ -230,7 +230,7 @@ contract CentrifugeConnector is Auth { // check if liquidity pool currency is supported by the centrifuge pool require(_poolCurrencyCheck(lPool.poolId(), lPool.asset()), "CentrifugeConnector/currency-not-supported"); // check if user is allowed to hold the restriced liquidity pool tokens - require(_trancheTokenCheck(lPool.poolId(), lPool.trancheId(), lPool.asset(), _user), "CentrifugeConnector/tranche-tokens-not-supported"); + require(_liquidityPoolTokensCheck(lPool.poolId(), lPool.trancheId(), lPool.asset(), _user), "CentrifugeConnector/tranche-tokens-not-supported"); if (lpValues.openRedeem > 0) { // cancel outstanding redeem orders gateway.decreaseRedeemOrder(lPool.poolId(), lPool.trancheId(), _user, currencyAddressToId[lPool.asset()], lpValues.openRedeem); @@ -408,7 +408,7 @@ contract CentrifugeConnector is Auth { } } - function handleCollectInvest(uint64 _poolId, bytes16 _trancheId, address _recepient, uint128 _currency, uint128 _currencyInvested, uint128 _tokensPayout, uint128 _remainingInvestOrder) public onlyGateway { + function handleExecutedCollectInvest(uint64 _poolId, bytes16 _trancheId, address _recepient, uint128 _currency, uint128 _currencyInvested, uint128 _tokensPayout, uint128 _remainingInvestOrder) public onlyGateway { require(_currencyInvested != 0, "CentrifugeConnector/zero-invest"); address currency = currencyIdToAddress[_currency]; address lPool = liquidityPools[_poolId][_trancheId][currency]; @@ -422,7 +422,7 @@ contract CentrifugeConnector is Auth { LiquidityPoolLike(lPool).mint(address(escrow), _tokensPayout); // mint to escrow. Recepeint can claim by calling withdraw / redeem } - function handleCollectRedeem(uint64 _poolId, bytes16 _trancheId, address _recepient, uint128 _currency, uint128 _currencyPayout, uint128 _trancheTokensRedeemed, uint128 _remainingRedeemOrder) public onlyGateway { + function handleExecutedCollectRedeem(uint64 _poolId, bytes16 _trancheId, address _recepient, uint128 _currency, uint128 _currencyPayout, uint128 _trancheTokensRedeemed, uint128 _remainingRedeemOrder) public onlyGateway { require(_trancheTokensRedeemed != 0, "CentrifugeConnector/zero-redeem"); address currency = currencyIdToAddress[_currency]; address lPool = liquidityPools[_poolId][_trancheId][currency]; @@ -436,7 +436,7 @@ contract CentrifugeConnector is Auth { LiquidityPoolLike(lPool).burn(address(escrow), _trancheTokensRedeemed); // burned redeemed tokens from escrow } - function handleDecreaseInvestOrder(uint64 _poolId, bytes16 _trancheId, address _user, uint128 _currency, uint128 _currencyPayout, uint128 _remainingInvestOrder) public onlyGateway { + function handleExecutedDecreaseInvestOrder(uint64 _poolId, bytes16 _trancheId, address _user, uint128 _currency, uint128 _currencyPayout, uint128 _remainingInvestOrder) public onlyGateway { require(_currencyPayout != 0, "CentrifugeConnector/zero-payout"); address currency = currencyIdToAddress[_currency]; LiquidityPoolLike lPool = LiquidityPoolLike(liquidityPools[_poolId][_trancheId][currency]); @@ -451,7 +451,7 @@ contract CentrifugeConnector is Auth { orderbook[_user][address(lPool)].openInvest = _remainingInvestOrder; } - function handleDecreaseRedeemOrder(uint64 _poolId, bytes16 _trancheId, address _user, uint128 _currency, uint128 _tokensPayout, uint128 _remainingRedeemOrder) public onlyGateway { + function handleExecutedDecreaseRedeemOrder(uint64 _poolId, bytes16 _trancheId, address _user, uint128 _currency, uint128 _tokensPayout, uint128 _remainingRedeemOrder) public onlyGateway { require(_tokensPayout != 0, "CentrifugeConnector/zero-payout"); address currency = currencyIdToAddress[_currency]; LiquidityPoolLike lPool = LiquidityPoolLike(liquidityPools[_poolId][_trancheId][currency]); @@ -652,7 +652,7 @@ contract CentrifugeConnector is Auth { return true; } - function _trancheTokenCheck(uint64 _poolId, bytes16 _trancheId, address _currency, address _user) internal returns (bool) { + function _liquidityPoolTokensCheck(uint64 _poolId, bytes16 _trancheId, address _currency, address _user) internal returns (bool) { LiquidityPoolLike lPool = LiquidityPoolLike(liquidityPools[_poolId][_trancheId][_currency]); require(address(lPool) != address(0), "CentrifugeConnector/unknown-liquidity-pool"); require(lPool.hasMember(_user), "CentrifugeConnector/not-a-member"); @@ -664,7 +664,7 @@ contract CentrifugeConnector is Auth { if (values.maxDeposit < _currency) { values.maxDeposit = 0; } else { - values.maxDeposit = values.maxDeposit - _currency; + values.maxDeposit = values.maxDeposit - _currency; } if (values.maxMint < _trancheTokens) { values.maxMint = 0; @@ -696,50 +696,4 @@ contract CentrifugeConnector is Auth { value = uint128(_value); } } - - function handleExecutedDecreaseInvestOrder( - uint64 poolId, - bytes16 trancheId, - bytes32 investor, - uint128 currency, - uint128 currencyPayout, - uint128 remainingInvestOrder - ) public onlyGateway { - // TODO: Implement - } - - function handleExecutedDecreaseRedeemOrder( - uint64 poolId, - bytes16 trancheId, - bytes32 investor, - uint128 currency, - uint128 trancheTokensPayout, - uint128 remainingRedeemOrder - ) public onlyGateway { - // TODO: Implement - } - - function handleExecutedCollectInvest( - uint64 poolId, - bytes16 trancheId, - bytes32 investor, - uint128 currency, - uint128 currencyPayout, - uint128 trancheTokensPayout, - uint128 remainingInvestOrder - ) public onlyGateway { - // TODO: Implement - } - - function handleExecutedCollectRedeem( - uint64 poolId, - bytes16 trancheId, - bytes32 investor, - uint128 currency, - uint128 currencyPayout, - uint128 trancheTokensPayout, - uint128 remainingRedeemOrder - ) public onlyGateway { - // TODO: Implement - } } From 901cefb91a13737ea4bf5701eaff7aef29308e2a Mon Sep 17 00:00:00 2001 From: Alina Sinelnikova Date: Wed, 28 Jun 2023 09:50:16 +0100 Subject: [PATCH 20/24] Apply suggestions from code review Co-authored-by: Jeroen Offerijns --- src/liquidityPool/Factory.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/liquidityPool/Factory.sol b/src/liquidityPool/Factory.sol index 8c1269ca..7ab91b20 100644 --- a/src/liquidityPool/Factory.sol +++ b/src/liquidityPool/Factory.sol @@ -17,13 +17,13 @@ contract LiquidityPoolFactory { public returns (address) { - // Salt is hash(poolId + trancheId + asset), to deploy copies of the restricted token contract - // on multiple chains with the same address for the same tranche + // Salt is hash(poolId + trancheId + asset), to deploy copies of the liquidity pool contract + // on multiple chains with the same address for the same tranche and asset bytes32 salt = keccak256(abi.encodePacked(_poolId, _trancheId, _currencyId)); LiquidityPool lPool = new LiquidityPool{salt: salt}(_decimals); - // Name and symbol are not passed on constructor, such that if the same tranche is deployed + // Name and symbol are not passed on constructor, such that if the same liquidity pool is deployed // on another chain with a different name (it might have changed in between deployments), // then the address remains deterministic. lPool.file("name", _name); From 1a3e71c0029224bfc50e0f4948c89c2e6b1f5843 Mon Sep 17 00:00:00 2001 From: ilinzweilin Date: Wed, 28 Jun 2023 11:25:56 +0200 Subject: [PATCH 21/24] improve liquidity pool docs --- src/liquidityPool/LiquidityPool.sol | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/liquidityPool/LiquidityPool.sol b/src/liquidityPool/LiquidityPool.sol index 3561f733..28ead250 100644 --- a/src/liquidityPool/LiquidityPool.sol +++ b/src/liquidityPool/LiquidityPool.sol @@ -1,15 +1,15 @@ // SPDX-License-Identifier: AGPL-3.0-only pragma solidity ^0.8.18; -// Tranche implementation for Centrifuge Pools following the EIP4626 standard. -// Each Tranche is a tokenized vault issuing shares as restricted ERC20 tokens against stable currency deposits based on the current share price. -// tranche vault: tranche asset value. -// asset: The underlying stable currency of the Centrifuge pool. -// share: The restricted ERC-20 Tranche token (TIN/DROP). Has a ratio (token price) of underlying assets exchanged on deposit/withdraw/redeem +// Liquidity Pool implementation for Centrifuge Pools following the EIP4626 standard. +// Each Liquidity Pool is a tokenized vault issuing shares as restricted ERC20 tokens against stable currency deposits based on the current share price. +// Liquidity Pool vault: Liquidity Pool asset value. +// asset: The underlying stable currency of the Liquidity Pool. Note: 1 Centrifuge Pool can have multiple Liquidity Pools for the same Tranche token with different underlying currencies (assets). +// share: The restricted ERC-20 Liquidity pool token. Has a ratio (token price) of underlying assets exchanged on deposit/withdraw/redeem. Liquidity pool tokens on evm represent tranche tokens on centrifuge chain (even though in the current implementation one tranche token on centrifuge chain can be split across multiple liquidity pool tokens on EVM). // Challenges: // 1. Centrifuge Pools and corresponding Tranches live on Centchain having their liquidity spread across multiple chains. -// Latest Tranche values, like share / token price, tranche asset value, total assets... have to be retrieved from Centrifuge chain in order to provide share <-> asset conversions. +// Latest Tranche Token token price is not available in the same block and is updated in an async manner from Centrifuge chain. Deposit & Redemption previews can only be made based on the latest price updates from Centrifuge chain. // 2. Pool Epochs: Deposits into and redemptions from Centrifuge Pools are subject to epochs. Deposit and redemption orders are collected during 24H epoch periods // and filled during epoch execution following the rules of the underlying pool. Consequently, deposits and redemptions are not instanty possible and have to follow the epoch schedule. // LiquidityPool is extending the EIP4626 standard by 'requestRedeem' & 'requestDeposit' functions, where redeem and deposit orders are submitted to the pools to be included in the execution of the following epoch. @@ -65,7 +65,7 @@ contract LiquidityPool is RestrictedToken { emit File(_what, _data); } - /// @dev Centrifuge chain pool information to be files by factory on deployment + /// @dev Centrifuge chain pool information to be filed by factory on deployment function setPoolDetails(uint64 _poolId, bytes16 _trancheId) public auth { require(poolId == 0, "LiquidityPool/pool-details-already-set"); poolId = _poolId; From 8319eef21db1bcc879f06ec10ca4b30fcffde803 Mon Sep 17 00:00:00 2001 From: ilinzweilin Date: Wed, 16 Aug 2023 17:37:30 +0100 Subject: [PATCH 22/24] EIP4626 deployment mvp --- .../{Connector-Axelar.s.sol => Axelar.s.sol} | 38 +- script/{Connector-XCM.s.sol => XCM.s.sol} | 36 +- src/Escrow.sol | 2 +- src/{routers => }/Gateway.sol | 201 +++-- src/{Connector.sol => InvestmentManager.sol} | 336 ++++---- src/Messages.sol | 88 +- src/admin/DelayedAdmin.sol | 10 +- src/admin/PauseAdmin.sol | 10 +- src/liquidityPool/Factory.sol | 14 +- src/liquidityPool/LiquidityPool.sol | 56 +- src/routers/axelar/Router.sol | 28 +- src/routers/xcm/Router.sol | 46 +- test/Admin.t.sol | 98 +-- test/Connector.t.sol | 804 ------------------ test/InvestmentManager.t.sol | 742 ++++++++++++++++ test/LiquidityPool.t.sol | 340 ++++++++ test/Messages.t.sol | 334 ++++---- test/Misc.t.sol | 2 +- test/accounts/PoolManager.sol | 12 +- ...nnector.sol => MockHomeLiquidityPools.sol} | 55 +- test/mock/MockXcmRouter.sol | 8 +- 21 files changed, 1763 insertions(+), 1497 deletions(-) rename script/{Connector-Axelar.s.sol => Axelar.s.sol} (54%) rename script/{Connector-XCM.s.sol => XCM.s.sol} (57%) rename src/{routers => }/Gateway.sol (57%) rename src/{Connector.sol => InvestmentManager.sol} (73%) delete mode 100644 test/Connector.t.sol create mode 100644 test/InvestmentManager.t.sol create mode 100644 test/LiquidityPool.t.sol rename test/mock/{MockHomeConnector.sol => MockHomeLiquidityPools.sol} (61%) diff --git a/script/Connector-Axelar.s.sol b/script/Axelar.s.sol similarity index 54% rename from script/Connector-Axelar.s.sol rename to script/Axelar.s.sol index 01b227eb..c34b455a 100644 --- a/script/Connector-Axelar.s.sol +++ b/script/Axelar.s.sol @@ -1,17 +1,17 @@ // SPDX-License-Identifier: AGPL-3.0-only pragma solidity ^0.8.18; -import {ConnectorAxelarRouter} from "src/routers/axelar/Router.sol"; -import {ConnectorGateway} from "src/routers/Gateway.sol"; -import {CentrifugeConnector} from "src/Connector.sol"; -import {ConnectorEscrow} from "src/Escrow.sol"; -import {ConnectorPauseAdmin} from "src/admin/PauseAdmin.sol"; -import {ConnectorDelayedAdmin} from "src/admin/DelayedAdmin.sol"; +import {AxelarRouter} from "src/routers/axelar/Router.sol"; +import {Gateway} from "src/Gateway.sol"; +import {InvestmentManager} from "src/InvestmentManager.sol"; +import {Escrow} from "src/Escrow.sol"; +import {PauseAdmin} from "src/admin/PauseAdmin.sol"; +import {DelayedAdmin} from "src/admin/DelayedAdmin.sol"; import {TrancheTokenFactory, MemberlistFactory} from "src/token/factory.sol"; import "forge-std/Script.sol"; // Script to deploy Connectors with an Axelar router. -contract ConnectorAxelarScript is Script { +contract AxelarScript is Script { // address(0)[0:20] + keccak("Centrifuge")[21:32] bytes32 SALT = 0x000000000000000000000000000000000000000075eb27011b69f002dc094d05; @@ -25,27 +25,27 @@ contract ConnectorAxelarScript is Script { uint256 gracePeriod = 48 hours; address tokenFactory_ = address(new TrancheTokenFactory{ salt: SALT }()); address memberlistFactory_ = address(new MemberlistFactory{ salt: SALT }()); - address escrow_ = address(new ConnectorEscrow{ salt: SALT }()); - CentrifugeConnector connector = - new CentrifugeConnector{ salt: SALT }(escrow_, tokenFactory_, memberlistFactory_); + address escrow_ = address(new Escrow{ salt: SALT }()); + InvestmentManager investmentManager = + new InvestmentManager{ salt: SALT }(escrow_, tokenFactory_, memberlistFactory_); - ConnectorAxelarRouter router = new ConnectorAxelarRouter{ salt: SALT }( - address(connector), + AxelarRouter router = new AxelarRouter{ salt: SALT }( + address(investmentManager), address(vm.envAddress("AXELAR_GATEWAY")) ); - connector.file("router", address(router)); - ConnectorPauseAdmin pauseAdmin = new ConnectorPauseAdmin(); - ConnectorDelayedAdmin delayedAdmin = new ConnectorDelayedAdmin(); - ConnectorGateway gateway = - new ConnectorGateway{ salt: SALT }(address(connector), address(router), shortWait, longWait, gracePeriod); + investmentManager.file("router", address(router)); + PauseAdmin pauseAdmin = new PauseAdmin(); + DelayedAdmin delayedAdmin = new DelayedAdmin(); + Gateway gateway = + new Gateway{ salt: SALT }(address(investmentManager), address(router), shortWait, longWait, gracePeriod); gateway.rely(address(pauseAdmin)); gateway.rely(address(delayedAdmin)); pauseAdmin.file("gateway", address(gateway)); delayedAdmin.file("gateway", address(gateway)); router.file("gateway", address(gateway)); - connector.rely(address(gateway)); + investmentManager.rely(address(gateway)); router.rely(address(gateway)); - ConnectorEscrow(address(escrow_)).rely(address(gateway)); + Escrow(address(escrow_)).rely(address(gateway)); // TODO: rely pauseMultisig on pauseAdmin pauseAdmin.rely(address(0)); diff --git a/script/Connector-XCM.s.sol b/script/XCM.s.sol similarity index 57% rename from script/Connector-XCM.s.sol rename to script/XCM.s.sol index f31f6eca..3f47cc40 100644 --- a/script/Connector-XCM.s.sol +++ b/script/XCM.s.sol @@ -1,17 +1,17 @@ // SPDX-License-Identifier: AGPL-3.0-only pragma solidity ^0.8.18; -import {ConnectorXCMRouter} from "src/routers/xcm/Router.sol"; -import {ConnectorGateway} from "src/routers/Gateway.sol"; -import {CentrifugeConnector} from "src/Connector.sol"; -import {ConnectorEscrow} from "src/Escrow.sol"; -import {ConnectorPauseAdmin} from "src/admin/PauseAdmin.sol"; -import {ConnectorDelayedAdmin} from "src/admin/DelayedAdmin.sol"; +import {XCMRouter} from "src/routers/xcm/Router.sol"; +import {Gateway} from "src/Gateway.sol"; +import {InvestmentManager} from "src/InvestmentManager.sol"; +import {Escrow} from "src/Escrow.sol"; +import {PauseAdmin} from "src/admin/PauseAdmin.sol"; +import {DelayedAdmin} from "src/admin/DelayedAdmin.sol"; import {TrancheTokenFactory, MemberlistFactory} from "src/token/factory.sol"; import "forge-std/Script.sol"; // Script to deploy Connectors with an XCM router. -contract ConnectorXCMScript is Script { +contract XCMScript is Script { // address(0)[0:20] + keccak("Centrifuge")[21:32] bytes32 SALT = 0x000000000000000000000000000000000000000075eb27011b69f002dc094d05; @@ -25,28 +25,28 @@ contract ConnectorXCMScript is Script { uint256 gracePeriod = 48 hours; address tokenFactory_ = address(new TrancheTokenFactory{ salt: SALT }()); address memberlistFactory_ = address(new MemberlistFactory{ salt: SALT }()); - address escrow_ = address(new ConnectorEscrow{ salt: SALT }()); - CentrifugeConnector connector = - new CentrifugeConnector{ salt: SALT }(escrow_, tokenFactory_, memberlistFactory_); + address escrow_ = address(new Escrow{ salt: SALT }()); + InvestmentManager investmentManager = + new InvestmentManager{ salt: SALT }(escrow_, tokenFactory_, memberlistFactory_); - ConnectorXCMRouter router = new ConnectorXCMRouter{ salt: SALT }( + XCMRouter router = new XCMRouter{ salt: SALT }( address(vm.envAddress("CENTRIFUGE_CHAIN_ORIGIN")), uint8(vm.envUint("CENTRIFUGE_CHAIN_CONNECTORS_PALLET_INDEX")), uint8(vm.envUint("CENTRIFUGE_CHAIN_CONNECTORS_PALLET_HANDLE_INDEX")) ); - connector.file("router", address(router)); - ConnectorPauseAdmin pauseAdmin = new ConnectorPauseAdmin(); - ConnectorDelayedAdmin delayedAdmin = new ConnectorDelayedAdmin(); - ConnectorGateway gateway = - new ConnectorGateway{ salt: SALT }(address(connector), address(router), shortWait, longWait, gracePeriod); + investmentManager.file("router", address(router)); + PauseAdmin pauseAdmin = new PauseAdmin(); + DelayedAdmin delayedAdmin = new DelayedAdmin(); + Gateway gateway = + new Gateway{ salt: SALT }(address(investmentManager), address(router), shortWait, longWait, gracePeriod); gateway.rely(address(pauseAdmin)); gateway.rely(address(delayedAdmin)); pauseAdmin.file("gateway", address(gateway)); delayedAdmin.file("gateway", address(gateway)); router.file("gateway", address(gateway)); - connector.rely(address(gateway)); + investmentManager.rely(address(gateway)); router.rely(address(gateway)); - ConnectorEscrow(address(escrow_)).rely(address(gateway)); + Escrow(address(escrow_)).rely(address(gateway)); // rely multisig on pauseAdmin pauseAdmin.rely(address(0)); diff --git a/src/Escrow.sol b/src/Escrow.sol index 92ff03ca..f2b6b319 100644 --- a/src/Escrow.sol +++ b/src/Escrow.sol @@ -8,7 +8,7 @@ interface ApproveLike { function approve(address, uint256) external; } -contract ConnectorEscrow is Auth { +contract Escrow is Auth { event Approve(address indexed token, address indexed spender, uint256 value); diff --git a/src/routers/Gateway.sol b/src/Gateway.sol similarity index 57% rename from src/routers/Gateway.sol rename to src/Gateway.sol index 9b9855ad..824735cc 100644 --- a/src/routers/Gateway.sol +++ b/src/Gateway.sol @@ -3,9 +3,10 @@ pragma solidity ^0.8.18; pragma abicoder v2; import {TypedMemView} from "memview-sol/TypedMemView.sol"; -import {ConnectorMessages} from "../Messages.sol"; +import {Messages} from "./Messages.sol"; +import "forge-std/Test.sol"; -interface ConnectorLike { +interface InvestmentManagerLike { function addCurrency(uint128 currency, address currencyAddress) external; function addPool(uint64 poolId) external; function allowPoolCurrency(uint64 poolId, uint128 currency) external; @@ -25,36 +26,32 @@ interface ConnectorLike { function handleExecutedDecreaseInvestOrder( uint64 poolId, bytes16 trancheId, - bytes32 investor, + address investor, uint128 currency, - uint128 currencyPayout, - uint128 remainingInvestOrder + uint128 currencyPayout ) external; function handleExecutedDecreaseRedeemOrder( uint64 poolId, bytes16 trancheId, - bytes32 investor, + address investor, uint128 currency, - uint128 trancheTokensPayout, - uint128 remainingRedeemOrder + uint128 trancheTokensPayout ) external; function handleExecutedCollectInvest( uint64 poolId, bytes16 trancheId, - bytes32 investor, + address investor, uint128 currency, uint128 currencyPayout, - uint128 trancheTokensPayout, - uint128 remainingInvestOrder + uint128 trancheTokensPayout ) external; function handleExecutedCollectRedeem( uint64 poolId, bytes16 trancheId, - bytes32 investor, + address investor, uint128 currency, uint128 currencyPayout, - uint128 trancheTokensPayout, - uint128 remainingRedeemOrder + uint128 trancheTokensPayout ) external; } @@ -66,11 +63,11 @@ interface AuthLike { function rely(address usr) external; } -contract ConnectorGateway { +contract Gateway is Test { using TypedMemView for bytes; // why bytes29? - https://github.com/summa-tx/memview-sol#why-bytes29 using TypedMemView for bytes29; - using ConnectorMessages for bytes29; + using Messages for bytes29; mapping(address => uint256) public wards; mapping(address => uint256) public relySchedule; @@ -80,7 +77,7 @@ contract ConnectorGateway { uint256 public immutable gracePeriod; bool public paused = false; - ConnectorLike public immutable connector; + InvestmentManagerLike public immutable investmentManager; // TODO: support multiple incoming routers (just a single outgoing router) to simplify router migrations RouterLike public immutable router; @@ -95,13 +92,13 @@ contract ConnectorGateway { event Unpause(); constructor( - address connector_, + address investmentManager_, address router_, uint256 shortScheduleWait_, uint256 longScheduleWait_, uint256 gracePeriod_ ) { - connector = ConnectorLike(connector_); + investmentManager = InvestmentManagerLike(investmentManager_); router = RouterLike(router_); shortScheduleWait = shortScheduleWait_; @@ -113,22 +110,22 @@ contract ConnectorGateway { } modifier auth() { - require(wards[msg.sender] == 1, "ConnectorGateway/not-authorized"); + require(wards[msg.sender] == 1, "Gateway/not-authorized"); _; } - modifier onlyConnector() { - require(msg.sender == address(connector), "ConnectorGateway/only-connector-allowed-to-call"); + modifier onlyInvestmentManager() { + require(msg.sender == address(investmentManager), "Gateway/only-investmentManager-allowed-to-call"); _; } modifier onlyRouter() { - require(msg.sender == address(router), "ConnectorGateway/only-router-allowed-to-call"); + require(msg.sender == address(router), "Gateway/only-router-allowed-to-call"); _; } modifier pauseable() { - require(!paused, "ConnectorGateway/paused"); + require(!paused, "Gateway/paused"); _; } @@ -169,9 +166,9 @@ contract ConnectorGateway { } function executeScheduledRely(address user) public { - require(relySchedule[user] != 0, "ConnectorGateway/user-not-scheduled"); - require(relySchedule[user] < block.timestamp, "ConnectorGateway/user-not-ready"); - require(relySchedule[user] + gracePeriod > block.timestamp, "ConnectorGateway/user-too-old"); + require(relySchedule[user] != 0, "Gateway/user-not-scheduled"); + require(relySchedule[user] < block.timestamp, "Gateway/user-not-ready"); + require(relySchedule[user] + gracePeriod > block.timestamp, "Gateway/user-too-old"); relySchedule[user] = 0; wards[user] = 1; emit Rely(user); @@ -189,13 +186,13 @@ contract ConnectorGateway { bytes32 destinationAddress, uint128 currencyId, uint128 amount - ) public onlyConnector pauseable { + ) public onlyInvestmentManager pauseable { router.send( - ConnectorMessages.formatTransferTrancheTokens( + Messages.formatTransferTrancheTokens( poolId, trancheId, addressToBytes32(sender), - ConnectorMessages.formatDomain(ConnectorMessages.Domain.Centrifuge), + Messages.formatDomain(Messages.Domain.Centrifuge), currencyId, destinationAddress, amount @@ -211,13 +208,13 @@ contract ConnectorGateway { uint128 currencyId, address destinationAddress, uint128 amount - ) public onlyConnector pauseable { + ) public onlyInvestmentManager pauseable { router.send( - ConnectorMessages.formatTransferTrancheTokens( + Messages.formatTransferTrancheTokens( poolId, trancheId, addressToBytes32(sender), - ConnectorMessages.formatDomain(ConnectorMessages.Domain.EVM, destinationChainId), + Messages.formatDomain(Messages.Domain.EVM, destinationChainId), currencyId, destinationAddress, amount @@ -225,72 +222,72 @@ contract ConnectorGateway { ); } - function transfer(uint128 token, address sender, bytes32 receiver, uint128 amount) public onlyConnector pauseable { - router.send(ConnectorMessages.formatTransfer(token, addressToBytes32(sender), receiver, amount)); + function transfer(uint128 token, address sender, bytes32 receiver, uint128 amount) public onlyInvestmentManager pauseable { + router.send(Messages.formatTransfer(token, addressToBytes32(sender), receiver, amount)); } function increaseInvestOrder(uint64 poolId, bytes16 trancheId, address investor, uint128 currency, uint128 amount) public - onlyConnector + onlyInvestmentManager pauseable { router.send( - ConnectorMessages.formatIncreaseInvestOrder(poolId, trancheId, addressToBytes32(investor), currency, amount) + Messages.formatIncreaseInvestOrder(poolId, trancheId, addressToBytes32(investor), currency, amount) ); } function decreaseInvestOrder(uint64 poolId, bytes16 trancheId, address investor, uint128 currency, uint128 amount) public - onlyConnector + onlyInvestmentManager pauseable { router.send( - ConnectorMessages.formatDecreaseInvestOrder(poolId, trancheId, addressToBytes32(investor), currency, amount) + Messages.formatDecreaseInvestOrder(poolId, trancheId, addressToBytes32(investor), currency, amount) ); } function increaseRedeemOrder(uint64 poolId, bytes16 trancheId, address investor, uint128 currency, uint128 amount) public - onlyConnector + onlyInvestmentManager pauseable { router.send( - ConnectorMessages.formatIncreaseRedeemOrder(poolId, trancheId, addressToBytes32(investor), currency, amount) + Messages.formatIncreaseRedeemOrder(poolId, trancheId, addressToBytes32(investor), currency, amount) ); } function decreaseRedeemOrder(uint64 poolId, bytes16 trancheId, address investor, uint128 currency, uint128 amount) public - onlyConnector + onlyInvestmentManager pauseable { router.send( - ConnectorMessages.formatDecreaseRedeemOrder(poolId, trancheId, addressToBytes32(investor), currency, amount) + Messages.formatDecreaseRedeemOrder(poolId, trancheId, addressToBytes32(investor), currency, amount) ); } - function collectInvest(uint64 poolId, bytes16 trancheId, address investor) public onlyConnector pauseable { - router.send(ConnectorMessages.formatCollectInvest(poolId, trancheId, addressToBytes32(investor))); + function collectInvest(uint64 poolId, bytes16 trancheId, address investor, uint128 currency) public onlyInvestmentManager pauseable { + router.send(Messages.formatCollectInvest(poolId, trancheId, addressToBytes32(investor), currency)); } - function collectRedeem(uint64 poolId, bytes16 trancheId, address investor) public onlyConnector pauseable { - router.send(ConnectorMessages.formatCollectRedeem(poolId, trancheId, addressToBytes32(investor))); + function collectRedeem(uint64 poolId, bytes16 trancheId, address investor, uint128 currency) public onlyInvestmentManager pauseable { + router.send(Messages.formatCollectRedeem(poolId, trancheId, addressToBytes32(investor), currency)); } // --- Incoming --- function handle(bytes memory _message) external onlyRouter pauseable { bytes29 _msg = _message.ref(0); - if (ConnectorMessages.isAddCurrency(_msg)) { - (uint128 currency, address currencyAddress) = ConnectorMessages.parseAddCurrency(_msg); - connector.addCurrency(currency, currencyAddress); - } else if (ConnectorMessages.isAddPool(_msg)) { - (uint64 poolId) = ConnectorMessages.parseAddPool(_msg); - connector.addPool(poolId); - } else if (ConnectorMessages.isAllowPoolCurrency(_msg)) { - (uint64 poolId, uint128 currency) = ConnectorMessages.parseAllowPoolCurrency(_msg); - connector.allowPoolCurrency(poolId, currency); - } else if (ConnectorMessages.isAddTranche(_msg)) { + if (Messages.isAddCurrency(_msg)) { + (uint128 currency, address currencyAddress) = Messages.parseAddCurrency(_msg); + investmentManager.addCurrency(currency, currencyAddress); + } else if (Messages.isAddPool(_msg)) { + (uint64 poolId) = Messages.parseAddPool(_msg); + investmentManager.addPool(poolId); + } else if (Messages.isAllowPoolCurrency(_msg)) { + (uint64 poolId, uint128 currency) = Messages.parseAllowPoolCurrency(_msg); + investmentManager.allowPoolCurrency(poolId, currency); + } else if (Messages.isAddTranche(_msg)) { ( uint64 poolId, bytes16 trancheId, @@ -298,77 +295,75 @@ contract ConnectorGateway { string memory tokenSymbol, uint8 decimals, uint128 price - ) = ConnectorMessages.parseAddTranche(_msg); - connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); - } else if (ConnectorMessages.isUpdateMember(_msg)) { + ) = Messages.parseAddTranche(_msg); + investmentManager.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); + } else if (Messages.isUpdateMember(_msg)) { (uint64 poolId, bytes16 trancheId, address user, uint64 validUntil) = - ConnectorMessages.parseUpdateMember(_msg); - connector.updateMember(poolId, trancheId, user, validUntil); - } else if (ConnectorMessages.isUpdateTrancheTokenPrice(_msg)) { - (uint64 poolId, bytes16 trancheId, uint128 price) = ConnectorMessages.parseUpdateTrancheTokenPrice(_msg); - connector.updateTokenPrice(poolId, trancheId, price); - } else if (ConnectorMessages.isTransfer(_msg)) { - (uint128 currency, address recipient, uint128 amount) = ConnectorMessages.parseIncomingTransfer(_msg); - connector.handleTransfer(currency, recipient, amount); - } else if (ConnectorMessages.isTransferTrancheTokens(_msg)) { - (uint64 poolId, bytes16 trancheId, uint128 currencyId, address destinationAddress, uint128 amount) = - ConnectorMessages.parseTransferTrancheTokens20(_msg); - connector.handleTransferTrancheTokens(poolId, trancheId, currencyId, destinationAddress, amount); - } else if (ConnectorMessages.isExecutedDecreaseInvestOrder(_msg)) { + Messages.parseUpdateMember(_msg); + investmentManager.updateMember(poolId, trancheId, user, validUntil); + } else if (Messages.isUpdateTrancheTokenPrice(_msg)) { + (uint64 poolId, bytes16 trancheId, uint128 price) = Messages.parseUpdateTrancheTokenPrice(_msg); + investmentManager.updateTokenPrice(poolId, trancheId, price); + } else if (Messages.isTransfer(_msg)) { + (uint128 currency, address recipient, uint128 amount) = Messages.parseIncomingTransfer(_msg); + investmentManager.handleTransfer(currency, recipient, amount); + } + // else if (Messages.isTransferTrancheTokens(_msg)) { + // (uint64 poolId, bytes16 trancheId, uint128 currencyId, address destinationAddress, uint128 amount) = + // Messages.parseTransferTrancheTokens20(_msg); + // investmentManager.handleTransferTrancheTokens(poolId, trancheId, currencyId, destinationAddress, amount); + // } + else if (Messages.isExecutedDecreaseInvestOrder(_msg)) { ( uint64 poolId, bytes16 trancheId, - bytes32 investor, + address investor, uint128 currency, - uint128 currencyPayout, - uint128 remainingInvestOrder - ) = ConnectorMessages.parseExecutedDecreaseInvestOrder(_msg); - connector.handleExecutedDecreaseInvestOrder( - poolId, trancheId, investor, currency, currencyPayout, remainingInvestOrder + uint128 currencyPayout + ) = Messages.parseExecutedDecreaseInvestOrder(_msg); + investmentManager.handleExecutedDecreaseInvestOrder( + poolId, trancheId, investor, currency, currencyPayout ); - } else if (ConnectorMessages.isExecutedDecreaseRedeemOrder(_msg)) { + } else if (Messages.isExecutedDecreaseRedeemOrder(_msg)) { ( uint64 poolId, bytes16 trancheId, - bytes32 investor, + address investor, uint128 currency, - uint128 trancheTokensPayout, - uint128 remainingRedeemOrder - ) = ConnectorMessages.parseExecutedDecreaseRedeemOrder(_msg); - connector.handleExecutedDecreaseRedeemOrder( - poolId, trancheId, investor, currency, trancheTokensPayout, remainingRedeemOrder + uint128 trancheTokensPayout + ) = Messages.parseExecutedDecreaseRedeemOrder(_msg); + investmentManager.handleExecutedDecreaseRedeemOrder( + poolId, trancheId, investor, currency, trancheTokensPayout ); - } else if (ConnectorMessages.isExecutedCollectInvest(_msg)) { + } else if (Messages.isExecutedCollectInvest(_msg)) { ( uint64 poolId, bytes16 trancheId, - bytes32 investor, + address investor, uint128 currency, uint128 currencyPayout, - uint128 trancheTokensPayout, - uint128 remainingInvestOrder - ) = ConnectorMessages.parseExecutedCollectInvest(_msg); - connector.handleExecutedCollectInvest( - poolId, trancheId, investor, currency, currencyPayout, trancheTokensPayout, remainingInvestOrder + uint128 trancheTokensPayout + ) = Messages.parseExecutedCollectInvest(_msg); + investmentManager.handleExecutedCollectInvest( + poolId, trancheId, investor, currency, currencyPayout, trancheTokensPayout ); - } else if (ConnectorMessages.isExecutedCollectRedeem(_msg)) { + } else if (Messages.isExecutedCollectRedeem(_msg)) { ( uint64 poolId, bytes16 trancheId, - bytes32 investor, + address investor, uint128 currency, uint128 currencyPayout, - uint128 trancheTokensRedeemed, - uint128 remainingRedeemOrder - ) = ConnectorMessages.parseExecutedCollectRedeem(_msg); - connector.handleExecutedCollectRedeem( - poolId, trancheId, investor, currency, currencyPayout, trancheTokensRedeemed, remainingRedeemOrder + uint128 trancheTokensRedeemed + ) = Messages.parseExecutedCollectRedeem(_msg); + investmentManager.handleExecutedCollectRedeem( + poolId, trancheId, investor, currency, currencyPayout, trancheTokensRedeemed ); - } else if (ConnectorMessages.isAddAdmin(_msg)) { - address spell = ConnectorMessages.parseAddAdmin(_msg); + } else if (Messages.isAddAdmin(_msg)) { + address spell = Messages.parseAddAdmin(_msg); scheduleShortRely(spell); } else { - revert("ConnectorGateway/invalid-message"); + revert("Gateway/invalid-message"); } } diff --git a/src/Connector.sol b/src/InvestmentManager.sol similarity index 73% rename from src/Connector.sol rename to src/InvestmentManager.sol index 34835828..78fbf05f 100644 --- a/src/Connector.sol +++ b/src/InvestmentManager.sol @@ -7,6 +7,7 @@ import { LiquidityPool } from "./liquidityPool/LiquidityPool.sol"; import { ERC20Like } from "./token/restricted.sol"; import { MemberlistLike } from "./token/memberlist.sol"; import "./auth/auth.sol"; +import "forge-std/Test.sol"; interface GatewayLike { function transferTrancheTokensToCentrifuge( @@ -21,6 +22,7 @@ interface GatewayLike { bytes16 trancheId, address sender, uint64 destinationChainId, + uint128 currencyId, address destinationAddress, uint128 amount ) external; @@ -33,8 +35,8 @@ interface GatewayLike { external; function decreaseRedeemOrder(uint64 poolId, bytes16 trancheId, address investor, uint128 currency, uint128 amount) external; - function collectInvest(uint64 poolId, bytes16 trancheId, address investor) external; - function collectRedeem(uint64 poolId, bytes16 trancheId, address investor) external; + function collectInvest(uint64 poolId, bytes16 trancheId, address investor, uint128 currency) external; + function collectRedeem(uint64 poolId, bytes16 trancheId, address investor, uint128 currency) external; function paused() external returns(bool); } @@ -71,11 +73,11 @@ struct Pool { struct Tranche { 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; - // important: the decimals of the leading pool currency. Liquidity Pool shares have to be denomatimated with the same precision. - uint8 decimals; address[] liquidityPools; } @@ -85,11 +87,9 @@ struct LPValues { uint128 maxMint; uint128 maxWithdraw; uint128 maxRedeem; - uint128 openRedeem; - uint128 openInvest; } -contract CentrifugeConnector is Auth { +contract InvestmentManager is Auth, Test { mapping(uint64 => Pool) public pools; // pools on centrifuge chain mapping(uint64 => mapping(bytes16 => Tranche)) public tranches; // centrifuge chain tranches @@ -108,8 +108,8 @@ contract CentrifugeConnector is Auth { LiquidityPoolFactoryLike public immutable liquidityPoolFactory; MemberlistFactoryLike public immutable memberlistFactory; - uint256 MAX_UINT256 = 2**256 - 1; - uint256 MAX_UINT128 = 2**128 - 1; + uint256 constant MAX_UINT256 = type(uint256).max; + uint128 constant MAX_UINT128 = type(uint128).max; // --- Events --- event File(bytes32 indexed what, address data); @@ -140,68 +140,68 @@ contract CentrifugeConnector is Auth { /// @dev checks whether a Centrifuge pool is active - can be used to prevent deposit / redemption requests to/from certain pools & avoid transfers from escrow related to inactive pools. modifier poolActive() { LiquidityPoolLike lPool = LiquidityPoolLike(msg.sender); - require(pools[lPool.poolId()].isActive, "CentrifugeConnector/pool-deactivated"); + require(pools[lPool.poolId()].isActive, "InvestmentManager/pool-deactivated"); _; } /// @dev checks whether gateway is active - can be used to prevent any interactions with centrifuge chain and stop all deposits & redemtions from escrow. - modifier connectorActive() { - require(gateway.paused(), "CentrifugeConnector/connector-deactivated"); + modifier gatewayActive() { + require(!gateway.paused(), "InvestmentManager/investmentManager-deactivated"); _; } /// @dev gateway must be message.sender. permissions check for incoming message handling. modifier onlyGateway() { - require(msg.sender == address(gateway), "CentrifugeConnector/not-the-gateway"); + require(msg.sender == address(gateway), "InvestmentManager/not-the-gateway"); _; } /// @dev liquidity pool must be message.sender. permissions check for liquidity pool gated functions. modifier onlyLiquidityPoolWard() { - require(liquidityPoolWards[msg.sender] == 1, "CentrifugeConnector/not-liquidity-pool"); + require(liquidityPoolWards[msg.sender] == 1, "InvestmentManager/not-liquidity-pool"); _; } // --- Administration --- function file(bytes32 what, address data) external auth { if (what == "gateway") gateway = GatewayLike(data); - else revert("CentrifugeConnector/file-unrecognized-param"); + else revert("InvestmentManager/file-unrecognized-param"); emit File(what, data); } /// @dev activate / deactivate pool function setPoolActive(uint64 _poolId, bool _isActive) external auth { Pool storage pool = pools[_poolId]; - require(pool.createdAt > 0, "CentrifugeConnector/invalid-pool"); + require(pool.createdAt > 0, "InvestmentManager/invalid-pool"); pool.isActive = _isActive; } // --- liquidity pool outgoing message handling --- - /// @dev request tranche token redemption. Liquidity pools have to request redemptions from the centrifuge chain before actual currency payouts can be done. /// The redemption requests are added to the order book on centrifuge chain. Once the next epoch is executed on centrifuge chain, liquidity pools can proceed with currency payouts in case their orders got fullfilled. /// @notice The user tranche tokens required to fullfill the redemption request have to be locked, even though the currency payout can only happen after epoch execution. /// This function automatically closed all the outstading investment orders for the user. - function requestRedeem(uint256 _trancheTokenAmount, address _user) poolActive connectorActive public onlyLiquidityPoolWard { + function requestRedeem(uint256 _trancheTokenAmount, address _user) poolActive gatewayActive public onlyLiquidityPoolWard { address _liquidityPool = msg.sender; LPValues storage lpValues = orderbook[_user][ _liquidityPool]; LiquidityPoolLike lPool = LiquidityPoolLike(_liquidityPool); uint128 trancheTokenAmount = _toUint128(_trancheTokenAmount); // check if liquidity pool currency is supported by the centrifuge pool - require(_poolCurrencyCheck(lPool.poolId(), lPool.asset()), "CentrifugeConnector/currency-not-supported"); + require(_poolCurrencyCheck(lPool.poolId(), lPool.asset()), "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), "CentrifugeConnector/tranche-tokens-not-supported"); + require(_liquidityPoolTokensCheck(lPool.poolId(), lPool.trancheId(), lPool.asset(), _user), "InvestmentManager/tranche-tokens-not-supported"); - if (lpValues.openInvest > 0) { // cancel outstanding deposit orders - gateway.decreaseInvestOrder(lPool.poolId(), lPool.trancheId(), _user, currencyAddressToId[lPool.asset()], lpValues.openInvest); - } + // todo: cancel outstanding order + // gateway.decreaseInvestOrder(lPool.poolId(), lPool.trancheId(), _user, currencyAddressToId[lPool.asset()], lpValues.openInvest); + // } + if(trancheTokenAmount == 0) { // case: outstanding deposit orders only needed to be cancelled return; } if(lpValues.maxMint >= trancheTokenAmount) { // case: user has unclaimed trancheTokens in escrow -> more than redemption request - uint128 userTrancheTokenPrice = calcCustomTrancheTokenPrice(_user, _liquidityPool); + uint128 userTrancheTokenPrice = calcDepositTrancheTokenPrice(_user, _liquidityPool); uint128 currencyAmount = trancheTokenAmount * userTrancheTokenPrice; _decreaseDepositLimits(_user, _liquidityPool, currencyAmount, trancheTokenAmount); } else { @@ -210,8 +210,8 @@ contract CentrifugeConnector is Auth { lpValues.maxMint = 0; // transfer the differene between required and locked tranche tokens from user to escrow - require(lPool.balanceOf(_user) >= transferAmount, "CentrifugeConnector/insufficient-tranche-token-balance"); - require(lPool.transferFrom(_user, address(escrow), transferAmount), "CentrifugeConnector/tranche-token-transfer-failed"); + require(lPool.balanceOf(_user) >= transferAmount, "InvestmentManager/insufficient-tranche-token-balance"); + require(lPool.transferFrom(_user, address(escrow), transferAmount), "InvestmentManager/tranche-token-transfer-failed"); } gateway.increaseRedeemOrder(lPool.poolId(), lPool.trancheId(), _user, currencyAddressToId[lPool.asset()], trancheTokenAmount); } @@ -220,7 +220,7 @@ contract CentrifugeConnector is Auth { /// The deposit requests are added to the order book on centrifuge chain. Once the next epoch is executed on centrifuge chain, liquidity pools can proceed with tranche token payouts in case their orders got fullfilled. /// @notice The user currency amount equired to fullfill the deposit request have to be locked, even though the tranche token payout can only happen after epoch execution. /// This function automatically closed all the outstading redemption orders for the user. - function requestDeposit(uint _currencyAmount, address _user) poolActive connectorActive public onlyLiquidityPoolWard { + function requestDeposit(uint _currencyAmount, address _user) poolActive gatewayActive public onlyLiquidityPoolWard { address _liquidityPool = msg.sender; LPValues storage lpValues = orderbook[_user][ _liquidityPool]; LiquidityPoolLike lPool = LiquidityPoolLike(_liquidityPool); @@ -228,18 +228,18 @@ contract CentrifugeConnector is Auth { uint128 currencyAmount = _toUint128(_currencyAmount); // check if liquidity pool currency is supported by the centrifuge pool - require(_poolCurrencyCheck(lPool.poolId(), lPool.asset()), "CentrifugeConnector/currency-not-supported"); + require(_poolCurrencyCheck(lPool.poolId(), lPool.asset()), "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), "CentrifugeConnector/tranche-tokens-not-supported"); + require(_liquidityPoolTokensCheck(lPool.poolId(), lPool.trancheId(), lPool.asset(), _user), "InvestmentManager/tranche-tokens-not-supported"); + + // todo: cancel outstanding order + // gateway.decreaseRedeemOrder(lPool.poolId(), lPool.trancheId(), _user, currencyAddressToId[lPool.asset()], lpValues.openRedeem); - if (lpValues.openRedeem > 0) { // cancel outstanding redeem orders - gateway.decreaseRedeemOrder(lPool.poolId(), lPool.trancheId(), _user, currencyAddressToId[lPool.asset()], lpValues.openRedeem); - } if(currencyAmount == 0) { // case: outstanding redemption orders only needed to be cancelled return; } if(lpValues.maxWithdraw >= currencyAmount) { // case: user has some claimable funds in escrow -> funds > depositRequest currencyAmount - uint128 userTrancheTokenPrice = calcCustomTrancheTokenPrice( _user, _liquidityPool); + uint128 userTrancheTokenPrice = calcRedeemTrancheTokenPrice( _user, _liquidityPool); uint128 trancheTokens = currencyAmount / userTrancheTokenPrice; _decreaseRedemptionLimits(_user, _liquidityPool, currencyAmount, trancheTokens); } else { @@ -248,75 +248,76 @@ contract CentrifugeConnector is Auth { lpValues.maxRedeem = 0; // transfer the differene between required and locked currency from user to escrow - require(currency.balanceOf(_user) >= transferAmount, "CentrifugeConnector/insufficient-balance"); - require(currency.transferFrom(_user, address(escrow), transferAmount), "CentrifugeConnector/currency-transfer-failed"); + require(currency.balanceOf(_user) >= transferAmount, "InvestmentManager/insufficient-balance"); + require(currency.transferFrom(_user, address(escrow), transferAmount), "InvestmentManager/currency-transfer-failed"); } gateway.increaseInvestOrder(lPool.poolId(), lPool.trancheId(), _user, currencyAddressToId[lPool.asset()], currencyAmount); } // --- public outgoing message handling --- - function collectInvest(uint64 _poolId, bytes16 _trancheId, address _currency) public { - LiquidityPoolLike lPool = LiquidityPoolLike(liquidityPools[_poolId][_trancheId][_currency]); - require(address(lPool) != address(0), "CentrifugeConnector/unknown-liquidity-pool"); - require(lPool.hasMember(msg.sender), "CentrifugeConnector/not-a-member"); - - gateway.collectInvest(_poolId, _trancheId, address(msg.sender)); + function collectInvest(uint64 _poolId, bytes16 _trancheId, address _user, address _currency) public onlyLiquidityPoolWard { + LiquidityPoolLike lPool = LiquidityPoolLike(msg.sender); + require(lPool.hasMember(_user), "InvestmentManager/not-a-member"); + require(_poolCurrencyCheck(_poolId, _currency), "InvestmentManager/currency-not-supported"); + gateway.collectInvest(_poolId, _trancheId, _user, currencyAddressToId[_currency]); } - function collectRedeem(uint64 _poolId, bytes16 _trancheId, address _currency) public { - LiquidityPoolLike lPool = LiquidityPoolLike(liquidityPools[_poolId][_trancheId][_currency]); - require(address(lPool) != address(0), "CentrifugeConnector/unknown-liquidity-pool"); - require(lPool.hasMember(msg.sender), "CentrifugeConnector/not-a-member"); - - gateway.collectRedeem(_poolId, _trancheId, address(msg.sender)); + function collectRedeem(uint64 _poolId, bytes16 _trancheId, address _user, address _currency) public onlyLiquidityPoolWard { + LiquidityPoolLike lPool = LiquidityPoolLike(msg.sender); + require(lPool.hasMember(_user), "InvestmentManager/not-a-member"); + require(_poolCurrencyCheck(_poolId, _currency), "InvestmentManager/currency-not-supported"); + gateway.collectRedeem(_poolId, _trancheId, _user, currencyAddressToId[_currency]); } function transfer(address currencyAddress, bytes32 recipient, uint128 amount) public { uint128 currency = currencyAddressToId[currencyAddress]; - require(currency != 0, "CentrifugeConnector/unknown-currency"); + require(currency != 0, "InvestmentManager/unknown-currency"); ERC20Like erc20 = ERC20Like(currencyAddress); - require(erc20.balanceOf(msg.sender) >= amount, "CentrifugeConnector/insufficient-balance"); - require(erc20.transferFrom(msg.sender, address(escrow), amount), "CentrifugeConnector/currency-transfer-failed"); + require(erc20.balanceOf(msg.sender) >= amount, "InvestmentManager/insufficient-balance"); + require(erc20.transferFrom(msg.sender, address(escrow), amount), "InvestmentManager/currency-transfer-failed"); gateway.transfer(currency, msg.sender, recipient, amount); } - function transferTrancheTokensToCentrifuge( - uint64 poolId, - bytes16 trancheId, - address currency, // we need this as there is liquidityPool per supported currency - bytes32 destinationAddress, - uint128 amount - ) public { - LiquidityPoolLike lPool = LiquidityPoolLike(liquidityPools[poolId][trancheId][currency]); - require(address(lPool) != address(0), "CentrifugeConnector/unknown-token"); - - require(lPool.balanceOf(msg.sender) >= amount, "CentrifugeConnector/insufficient-balance"); - lPool.burn(msg.sender, amount); - - gateway.transferTrancheTokensToCentrifuge(poolId, trancheId, msg.sender, destinationAddress, amount); - } - - function transferTrancheTokensToEVM( - uint64 poolId, - bytes16 trancheId, - address currency, - uint64 destinationChainId, - address destinationAddress, - uint128 amount - ) public { - LiquidityPoolLike lPool = LiquidityPoolLike(liquidityPools[poolId][trancheId][currency]); - require(address(lPool) != address(0), "CentrifugeConnector/unknown-token"); - - require(lPool.balanceOf(msg.sender) >= amount, "CentrifugeConnector/insufficient-balance"); - lPool.burn(msg.sender, amount); - - gateway.transferTrancheTokensToEVM( - poolId, trancheId, msg.sender, destinationChainId, destinationAddress, amount - ); - } + // function transferTrancheTokensToCentrifuge( + // uint64 poolId, + // bytes16 trancheId, + // address currency, // we need this as there is liquidityPool per supported currency + // bytes32 destinationAddress, + // uint128 amount + // ) public { + // LiquidityPoolLike lPool = LiquidityPoolLike(liquidityPools[poolId][trancheId][currency]); + // require(address(lPool) != address(0), "InvestmentManager/unknown-token"); + + // require(lPool.balanceOf(msg.sender) >= amount, "InvestmentManager/insufficient-balance"); + // lPool.burn(msg.sender, amount); + + // gateway.transferTrancheTokensToCentrifuge(poolId, trancheId, msg.sender, destinationAddress, amount); + // } + + // function transferTrancheTokensToEVM( + // uint64 poolId, + // bytes16 trancheId, + // address currency, + // uint64 destinationChainId, + // address destinationAddress, + // uint128 amount + // ) public { + // LiquidityPoolLike lPool = LiquidityPoolLike(liquidityPools[poolId][trancheId][currency]); + // require(address(lPool) != address(0), "InvestmentManager/unknown-token"); + + // require(lPool.balanceOf(msg.sender) >= amount, "InvestmentManager/insufficient-balance"); + // lPool.burn(msg.sender, amount); + + // uint128 currencyId = currencyAddressToId[currency]; + // require(currencyId != 0, "InvestmentManager/unknown-currency"); + + // gateway.transferTrancheTokensToEVM( + // poolId, trancheId, msg.sender, destinationChainId, currencyId, destinationAddress, amount + // ); + // } // --- Incoming message handling --- /// @dev a global chain agnostic currency index is maintained on centrifuge chain. This function maps a currency from the centrifuge chain index to its corresponding address on the evm chain. @@ -324,9 +325,9 @@ contract CentrifugeConnector is Auth { /// @notice this function can only be executed by the gateway contract. function addCurrency(uint128 currency, address currencyAddress) public onlyGateway { // currency index on the centrifuge chain side should start at 1 - require(currency > 0, "CentrifugeConnector/currency-id-has-to-be-greater-than-0"); - require(currencyIdToAddress[currency] == address(0), "CentrifugeConnector/currency-id-in-use"); - require(currencyAddressToId[currencyAddress] == 0, "CentrifugeConnector/currency-address-in-use"); + require(currency > 0, "InvestmentManager/currency-id-has-to-be-greater-than-0"); + require(currencyIdToAddress[currency] == address(0), "InvestmentManager/currency-id-in-use"); + require(currencyAddressToId[currencyAddress] == 0, "InvestmentManager/currency-address-in-use"); currencyIdToAddress[currency] = currencyAddress; currencyAddressToId[currencyAddress] = currency; @@ -340,7 +341,7 @@ contract CentrifugeConnector is Auth { /// @notice 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, "CentrifugeConnector/pool-already-added"); + require(pool.createdAt == 0, "InvestmentManager/pool-already-added"); pool.poolId = poolId; pool.createdAt = block.timestamp; pool.isActive = true; @@ -352,10 +353,10 @@ contract CentrifugeConnector is Auth { /// @notice 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, "CentrifugeConnector/invalid-pool"); + require(pool.createdAt > 0, "InvestmentManager/invalid-pool"); address currencyAddress = currencyIdToAddress[currency]; - require(currencyAddress != address(0), "CentrifugeConnector/unknown-currency"); + require(currencyAddress != address(0), "InvestmentManager/unknown-currency"); allowedPoolCurrencies[poolId][currencyAddress] = true; emit PoolCurrencyAllowed(currency, poolId); @@ -372,9 +373,9 @@ contract CentrifugeConnector is Auth { uint128 _price // not required here ) public onlyGateway { Pool storage pool = pools[_poolId]; - require(pool.createdAt > 0, "CentrifugeConnector/invalid-pool"); + require(pool.createdAt > 0, "InvestmentManager/invalid-pool"); Tranche storage tranche = tranches[_poolId][_trancheId]; - require(tranche.createdAt == 0, "CentrifugeConnector/tranche-already-exists"); + require(tranche.createdAt == 0, "InvestmentManager/tranche-already-exists"); tranche.poolId = _poolId; tranche.trancheId = _trancheId; @@ -388,112 +389,105 @@ contract CentrifugeConnector is Auth { function updateTokenPrice(uint64 _poolId, bytes16 _trancheId, uint128 _price) public onlyGateway { Tranche storage tranche = tranches[_poolId][_trancheId]; - require(tranche.createdAt > 0, "CentrifugeConnector/invalid-pool-or-tranche"); + require(tranche.createdAt > 0, "InvestmentManager/invalid-pool-or-tranche"); for (uint i=0; i 0, "CentrifugeConnector/invalid-pool-or-tranche"); + require(tranche.createdAt > 0, "InvestmentManager/invalid-pool-or-tranche"); for (uint i=0; i 0), "LiquidityPool/amount-exceeds-deposit-limits"); uint128 trancheTokenAmount = currencyAmount / userTrancheTokenPriceLP; _decreaseDepositLimits(_user, _liquidityPool, currencyAmount, trancheTokenAmount); // decrease user's deposit limits for this lp - require(lPool.hasMember( _user), "CentrifugeConnector/trancheTokens-not-a-member"); - require(lPool.transferFrom(address(escrow), _user, trancheTokenAmount), "CentrifugeConnector/trancheTokens-transfer-failed"); + require(lPool.hasMember( _user), "InvestmentManager/trancheTokens-not-a-member"); + require(lPool.transferFrom(address(escrow), _user, trancheTokenAmount), "InvestmentManager/trancheTokens-transfer-failed"); emit DepositProcessed(_liquidityPool, _user, currencyAmount); return uint256(trancheTokenAmount); @@ -546,19 +540,21 @@ contract CentrifugeConnector is Auth { /// Note: The tranche tokens are already minted on collectInvest and are deposited to the escrow account until the users calls mint, or deposit. /// @notice currencyAmount return value is type of uint256 to be compliant with EIP4626 LiquidityPool interface /// @return currencyAmount the amount of liquidityPool assets invested and locked in escrow in order for the amount of tranche received after successful investment into the pool. - function processMint(address _user, uint256 _trancheTokenAmount) poolActive connectorActive public onlyLiquidityPoolWard returns (uint256) { + function processMint(address _user, uint256 _trancheTokenAmount) poolActive gatewayActive public onlyLiquidityPoolWard returns (uint256) { address _liquidityPool = msg.sender; uint128 trancheTokenAmount = _toUint128(_trancheTokenAmount); + console.logUint(trancheTokenAmount); LiquidityPoolLike lPool = LiquidityPoolLike(_liquidityPool); - require((trancheTokenAmount <= orderbook[_user][ _liquidityPool].maxMint), "CentrifugeConnector/amount-exceeds-mint-limits"); - uint128 userTrancheTokenPriceLP = calcCustomTrancheTokenPrice(_user, _liquidityPool); + require((trancheTokenAmount <= orderbook[_user][ _liquidityPool].maxMint), "InvestmentManager/amount-exceeds-mint-limits"); + + uint128 userTrancheTokenPriceLP = calcDepositTrancheTokenPrice(_user, _liquidityPool); require((userTrancheTokenPriceLP > 0), "LiquidityPool/amount-exceeds-mint-limits"); uint128 currencyAmount = trancheTokenAmount * userTrancheTokenPriceLP; - + //console.logUint( currencyAmount); _decreaseDepositLimits(_user, _liquidityPool, currencyAmount, trancheTokenAmount); // decrease the possible deposit limits - require(lPool.hasMember( _user), "CentrifugeConnector/trancheTokens-not-a-member"); - require(lPool.transferFrom(address(escrow), _user, trancheTokenAmount), "CentrifugeConnector/trancheTokens-transfer-failed"); + require(lPool.hasMember( _user), "InvestmentManager/trancheTokens-not-a-member"); + require(lPool.transferFrom(address(escrow), _user, trancheTokenAmount), "InvestmentManager/trancheTokens-transfer-failed"); emit DepositProcessed(_liquidityPool, _user, currencyAmount); return uint256(currencyAmount); @@ -569,18 +565,18 @@ contract CentrifugeConnector is Auth { /// Note: The trancheToken amount required to fullfill the redemption order was already locked in escrow upon calling requestRedeem and burned upon collectRedeem. /// @notice currencyAmount return value is type of uint256 to be compliant with EIP4626 LiquidityPool interface /// @return currencyAmount the amount of liquidityPool assets received for the amount of redeemed/burned trancheTokens. - function processRedeem(uint256 _trancheTokenAmount, address _receiver, address _user) poolActive connectorActive public onlyLiquidityPoolWard returns (uint256) { + function processRedeem(uint256 _trancheTokenAmount, address _receiver, address _user) poolActive gatewayActive public onlyLiquidityPoolWard returns (uint256) { address _liquidityPool = msg.sender; uint128 trancheTokenAmount = _toUint128(_trancheTokenAmount); LiquidityPoolLike lPool = LiquidityPoolLike(_liquidityPool); - require((trancheTokenAmount <= orderbook[_user][ _liquidityPool].maxRedeem), "CentrifugeConnector/amount-exceeds-redeem-limits"); - uint128 userTrancheTokenPriceLP = calcCustomTrancheTokenPrice(_user, _liquidityPool); + require((trancheTokenAmount <= orderbook[_user][ _liquidityPool].maxRedeem), "InvestmentManager/amount-exceeds-redeem-limits"); + uint128 userTrancheTokenPriceLP = calcRedeemTrancheTokenPrice(_user, _liquidityPool); require((userTrancheTokenPriceLP > 0), "LiquidityPool/amount-exceeds-redemption-limits"); uint128 currencyAmount = trancheTokenAmount * userTrancheTokenPriceLP; _decreaseRedemptionLimits(_user, _liquidityPool, currencyAmount, trancheTokenAmount); // decrease the possible deposit limits - require(ERC20Like(lPool.asset()).transferFrom(address(escrow), _receiver, currencyAmount), "CentrifugeConnector/shares-transfer-failed"); + require(ERC20Like(lPool.asset()).transferFrom(address(escrow), _receiver, currencyAmount), "InvestmentManager/shares-transfer-failed"); emit RedemptionProcessed(_liquidityPool, _user, trancheTokenAmount); return uint256(currencyAmount); @@ -591,22 +587,21 @@ contract CentrifugeConnector is Auth { /// Note: The trancheToken amount required to fullfill the redemption order was already locked in escrow upon calling requestRedeem and burned upon collectRedeem. /// @notice trancheTokenAmount return value is type of uint256 to be compliant with EIP4626 LiquidityPool interface /// @return trancheTokenAmount the amount of trancheTokens redeemed/burned required to receive the currencyAmount payout/withdrawel. - function processWithdraw(uint256 _currencyAmount, address _receiver, address _user) poolActive connectorActive public onlyLiquidityPoolWard returns (uint256) { + function processWithdraw(uint256 _currencyAmount, address _receiver, address _user) poolActive gatewayActive public onlyLiquidityPoolWard returns (uint256) { address _liquidityPool = msg.sender; uint128 currencyAmount = _toUint128(_currencyAmount); LiquidityPoolLike lPool = LiquidityPoolLike(_liquidityPool); - require((currencyAmount <= orderbook[_user][ _liquidityPool].maxWithdraw), "CentrifugeConnector/amount-exceeds-withdraw-limits"); - uint128 userTrancheTokenPriceLP = calcCustomTrancheTokenPrice(_user, _liquidityPool); + require((currencyAmount <= orderbook[_user][ _liquidityPool].maxWithdraw), "InvestmentManager/amount-exceeds-withdraw-limits"); + uint128 userTrancheTokenPriceLP = calcRedeemTrancheTokenPrice(_user, _liquidityPool); require((userTrancheTokenPriceLP > 0), "LiquidityPool/amount-exceeds-withdraw-limits"); uint128 trancheTokenAmount = currencyAmount / userTrancheTokenPriceLP; _decreaseRedemptionLimits(_user, _liquidityPool, currencyAmount, trancheTokenAmount); - require(ERC20Like(lPool.asset()).transferFrom(address(escrow), _receiver, currencyAmount), "CentrifugeConnector/trancheTokens-transfer-failed"); + require(ERC20Like(lPool.asset()).transferFrom(address(escrow), _receiver, currencyAmount), "InvestmentManager/trancheTokens-transfer-failed"); return uint256(trancheTokenAmount); } - // ----- public functions function deployLiquidityPool( uint64 _poolId, @@ -615,11 +610,11 @@ contract CentrifugeConnector is Auth { ) public returns (address) { address liquidityPool = liquidityPools[_poolId][_trancheId][_currency]; - require(liquidityPool == address(0), "CentrifugeConnector/liquidityPool-already-deployed"); - require(pools[_poolId].createdAt > 0, "CentrifugeConnector/pool-does-not-exist"); + 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, "CentrifugeConnector/tranche-does-not-exist"); // tranche must have been added - require(_poolCurrencyCheck(_poolId, _currency), "CentrifugeConnector/currency-not-supported"); // currency must be supported by pool + require(tranche.createdAt != 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 = currencyAddressToId[_currency]; // deploy liquidity pool set gateway as admin on liquidityPool & memberlist @@ -637,25 +632,34 @@ contract CentrifugeConnector is Auth { } // ------ helper functions - function calcCustomTrancheTokenPrice(address _user, address _liquidityPool) public view returns (uint128 userTrancheTokenPrice) { + // TODO: check rounding + function calcDepositTrancheTokenPrice(address _user, address _liquidityPool) public view returns (uint128 userTrancheTokenPrice) { LPValues storage lpValues = orderbook[_user][_liquidityPool]; if(lpValues.maxMint == 0) { return 0; - } + } userTrancheTokenPrice = lpValues.maxDeposit / lpValues.maxMint; } + function calcRedeemTrancheTokenPrice(address _user, address _liquidityPool) public view returns (uint128 userTrancheTokenPrice) { + LPValues storage lpValues = orderbook[_user][_liquidityPool]; + if(lpValues.maxRedeem == 0) { + return 0; + } + userTrancheTokenPrice = lpValues.maxWithdraw / lpValues.maxRedeem; + } + function _poolCurrencyCheck(uint64 _poolId, address _currencyAddress) internal view returns (bool) { uint128 currency = currencyAddressToId[_currencyAddress]; - require(currency != 0, "CentrifugeConnector/unknown-currency"); // currency index on the centrifuge chain side should start at 1 - require(allowedPoolCurrencies[_poolId][_currencyAddress], "CentrifugeConnector/pool-currency-not-allowed"); + require(currency != 0, "InvestmentManager/unknown-currency"); // currency index on the centrifuge chain side should start at 1 + require(allowedPoolCurrencies[_poolId][_currencyAddress], "InvestmentManager/pool-currency-not-allowed"); return true; } function _liquidityPoolTokensCheck(uint64 _poolId, bytes16 _trancheId, address _currency, address _user) internal returns (bool) { LiquidityPoolLike lPool = LiquidityPoolLike(liquidityPools[_poolId][_trancheId][_currency]); - require(address(lPool) != address(0), "CentrifugeConnector/unknown-liquidity-pool"); - require(lPool.hasMember(_user), "CentrifugeConnector/not-a-member"); + require(address(lPool) != address(0), "InvestmentManager/unknown-liquidity-pool"); + require(lPool.hasMember(_user), "InvestmentManager/not-a-member"); return true; } @@ -676,7 +680,7 @@ contract CentrifugeConnector is Auth { function _decreaseRedemptionLimits(address _user, address _liquidityPool, uint128 _currency, uint128 _trancheTokens) internal { LPValues storage values = orderbook[_user][_liquidityPool]; if (values.maxWithdraw < _currency) { - values.maxDeposit = 0; + values.maxWithdraw = 0; } else { values.maxWithdraw = values.maxWithdraw - _currency; } diff --git a/src/Messages.sol b/src/Messages.sol index 48c51e7e..ec9e51ea 100644 --- a/src/Messages.sol +++ b/src/Messages.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.18; import "memview-sol/TypedMemView.sol"; -library ConnectorMessages { +library Messages { using TypedMemView for bytes; using TypedMemView for bytes29; @@ -23,7 +23,7 @@ library ConnectorMessages { UpdateTrancheTokenPrice, /// 6 - Update the member list of a tranche token with a new member UpdateMember, - /// 7 - A transfer of Stable Coins + /// 7 - A transfer of Stable CoinsformatTransferTrancheTokens Transfer, /// 8 - A transfer of Tranche tokens TransferTrancheTokens, @@ -334,7 +334,7 @@ library ConnectorMessages { return formatTransferTrancheTokens( poolId, trancheId, sender, destinationDomain, currencyId, bytes32(bytes20(destinationAddress)), amount ); - } + } function isTransferTrancheTokens(bytes29 _msg) internal pure returns (bool) { return messageType(_msg) == Call.TransferTrancheTokens; @@ -359,10 +359,10 @@ library ConnectorMessages { sender = bytes32(_msg.index(25, 32)); encodedDomain = bytes9(_msg.index(57, 9)); currencyId = uint128(_msg.indexUint(66, 16)); - destinationAddress = bytes32((_msg.index(83, 20))); - amount = uint128(_msg.indexUint(104, 16)); + destinationAddress = bytes32((_msg.index(82, 20))); + amount = uint128(_msg.indexUint(114, 16)); } - + // Parse a TransferTrancheTokens to an EVM-based `destinationAddress` (20-byte long). // We ignore the `sender` and the `domain` since it's not relevant when parsing an incoming message. function parseTransferTrancheTokens20(bytes29 _msg) @@ -376,11 +376,10 @@ library ConnectorMessages { // ignore: `sender` at bytes 25-56 // ignore: `domain` at bytes 57-65 currencyId = uint128(_msg.indexUint(66, 16)); - destinationAddress = address(bytes20(_msg.index(83, 20))); - amount = uint128(_msg.indexUint(104, 16)); + destinationAddress = address(bytes20(_msg.index(82, 20))); + amount = uint128(_msg.indexUint(114, 16)); } - /* * IncreaseInvestOrder Message * @@ -521,12 +520,12 @@ library ConnectorMessages { * 9-24: trancheId (16 bytes) * 25-56: investor address (32 bytes) */ - function formatCollectInvest(uint64 poolId, bytes16 trancheId, bytes32 investor) + function formatCollectInvest(uint64 poolId, bytes16 trancheId, bytes32 investor, uint128 currency) internal pure returns (bytes memory) { - return abi.encodePacked(uint8(Call.CollectInvest), poolId, trancheId, investor); + return abi.encodePacked(uint8(Call.CollectInvest), poolId, trancheId, investor, currency); } function isCollectInvest(bytes29 _msg) internal pure returns (bool) { @@ -536,11 +535,12 @@ library ConnectorMessages { function parseCollectInvest(bytes29 _msg) internal pure - returns (uint64 poolId, bytes16 trancheId, bytes32 investor) + returns (uint64 poolId, bytes16 trancheId, bytes32 investor, uint128 currency) { poolId = uint64(_msg.indexUint(1, 8)); trancheId = bytes16(_msg.index(9, 16)); investor = bytes32(_msg.index(25, 32)); + currency = uint128(_msg.indexUint(57, 16)); } /* @@ -551,12 +551,12 @@ library ConnectorMessages { * 9-24: trancheId (16 bytes) * 25-56: investor address (32 bytes) */ - function formatCollectRedeem(uint64 poolId, bytes16 trancheId, bytes32 investor) + function formatCollectRedeem(uint64 poolId, bytes16 trancheId, bytes32 investor, uint128 currency) internal pure returns (bytes memory) { - return abi.encodePacked(uint8(Call.CollectRedeem), poolId, trancheId, investor); + return abi.encodePacked(uint8(Call.CollectRedeem), poolId, trancheId, investor, currency); } function isCollectRedeem(bytes29 _msg) internal pure returns (bool) { @@ -566,11 +566,13 @@ library ConnectorMessages { function parseCollectRedeem(bytes29 _msg) internal pure - returns (uint64 poolId, bytes16 trancheId, bytes32 investor) + returns (uint64 poolId, bytes16 trancheId, bytes32 investor, uint128 currency) { poolId = uint64(_msg.indexUint(1, 8)); trancheId = bytes16(_msg.index(9, 16)); investor = bytes32(_msg.index(25, 32)); + currency = uint128(_msg.indexUint(57, 16)); + } function formatExecutedDecreaseInvestOrder( @@ -578,8 +580,7 @@ library ConnectorMessages { bytes16 trancheId, bytes32 investor, uint128 currency, - uint128 currencyPayout, - uint128 remainingInvestOrder + uint128 currencyPayout ) internal pure returns (bytes memory) { return abi.encodePacked( uint8(Call.ExecutedDecreaseInvestOrder), @@ -587,8 +588,7 @@ library ConnectorMessages { trancheId, investor, currency, - currencyPayout, - remainingInvestOrder + currencyPayout ); } @@ -602,18 +602,16 @@ library ConnectorMessages { returns ( uint64 poolId, bytes16 trancheId, - bytes32 investor, + address investor, uint128 currency, - uint128 currencyPayout, - uint128 remainingInvestOrder + uint128 currencyPayout ) { poolId = uint64(_msg.indexUint(1, 8)); trancheId = bytes16(_msg.index(9, 16)); - investor = bytes32(_msg.index(25, 32)); + investor = address(bytes20(_msg.index(25, 32))); currency = uint128(_msg.indexUint(57, 16)); currencyPayout = uint128(_msg.indexUint(73, 16)); - remainingInvestOrder = uint128(_msg.indexUint(89, 16)); } function formatExecutedDecreaseRedeemOrder( @@ -621,8 +619,7 @@ library ConnectorMessages { bytes16 trancheId, bytes32 investor, uint128 currency, - uint128 currencyPayout, - uint128 remainingRedeemOrder + uint128 currencyPayout ) internal pure returns (bytes memory) { return abi.encodePacked( uint8(Call.ExecutedDecreaseRedeemOrder), @@ -630,8 +627,7 @@ library ConnectorMessages { trancheId, investor, currency, - currencyPayout, - remainingRedeemOrder + currencyPayout ); } @@ -645,18 +641,16 @@ library ConnectorMessages { returns ( uint64 poolId, bytes16 trancheId, - bytes32 investor, + address investor, uint128 currency, - uint128 trancheTokensPayout, - uint128 remainingRedeemOrder + uint128 trancheTokensPayout ) { poolId = uint64(_msg.indexUint(1, 8)); trancheId = bytes16(_msg.index(9, 16)); - investor = bytes32(_msg.index(25, 32)); + investor = address(bytes20(_msg.index(25, 32))); currency = uint128(_msg.indexUint(57, 16)); trancheTokensPayout = uint128(_msg.indexUint(73, 16)); - remainingRedeemOrder = uint128(_msg.indexUint(89, 16)); } function formatExecutedCollectInvest( @@ -665,8 +659,7 @@ library ConnectorMessages { bytes32 investor, uint128 currency, uint128 currencyPayout, - uint128 trancheTokensPayout, - uint128 remainingInvestOrder + uint128 trancheTokensPayout ) internal pure returns (bytes memory) { return abi.encodePacked( uint8(Call.ExecutedCollectInvest), @@ -675,8 +668,7 @@ library ConnectorMessages { investor, currency, currencyPayout, - trancheTokensPayout, - remainingInvestOrder + trancheTokensPayout ); } @@ -690,20 +682,18 @@ library ConnectorMessages { returns ( uint64 poolId, bytes16 trancheId, - bytes32 investor, + address investor, uint128 currency, uint128 currencyPayout, - uint128 trancheTokensPayout, - uint128 remainingInvestOrder + uint128 trancheTokensPayout ) { poolId = uint64(_msg.indexUint(1, 8)); trancheId = bytes16(_msg.index(9, 16)); - investor = bytes32(_msg.index(25, 32)); + investor = address(bytes20(_msg.index(25, 32))); currency = uint128(_msg.indexUint(57, 16)); currencyPayout = uint128(_msg.indexUint(73, 16)); trancheTokensPayout = uint128(_msg.indexUint(89, 16)); - remainingInvestOrder = uint128(_msg.indexUint(105, 16)); } function formatExecutedCollectRedeem( @@ -712,8 +702,7 @@ library ConnectorMessages { bytes32 investor, uint128 currency, uint128 currencyPayout, - uint128 trancheTokensRedeemed, - uint128 remainingRedeemOrder + uint128 trancheTokensRedeemed ) internal pure returns (bytes memory) { return abi.encodePacked( uint8(Call.ExecutedCollectRedeem), @@ -722,8 +711,7 @@ library ConnectorMessages { investor, currency, currencyPayout, - trancheTokensRedeemed, - remainingRedeemOrder + trancheTokensRedeemed ); } @@ -737,20 +725,18 @@ library ConnectorMessages { returns ( uint64 poolId, bytes16 trancheId, - bytes32 investor, + address investor, uint128 currency, uint128 currencyPayout, - uint128 trancheTokensRedeemed, - uint128 remainingRedeemOrder + uint128 trancheTokensRedeemed ) { poolId = uint64(_msg.indexUint(1, 8)); trancheId = bytes16(_msg.index(9, 16)); - investor = bytes32(_msg.index(25, 32)); + investor = address(bytes20(_msg.index(25, 32))); currency = uint128(_msg.indexUint(57, 16)); currencyPayout = uint128(_msg.indexUint(73, 16)); trancheTokensRedeemed = uint128(_msg.indexUint(89, 16)); - remainingRedeemOrder = uint128(_msg.indexUint(105, 16)); } function formatAddAdmin(address user) internal pure returns (bytes memory) { diff --git a/src/admin/DelayedAdmin.sol b/src/admin/DelayedAdmin.sol index c1cef09c..b988ae2c 100644 --- a/src/admin/DelayedAdmin.sol +++ b/src/admin/DelayedAdmin.sol @@ -2,11 +2,11 @@ pragma solidity ^0.8.18; pragma abicoder v2; -import {ConnectorGateway} from "../routers/Gateway.sol"; +import {Gateway} from "../Gateway.sol"; import "./../auth/auth.sol"; -contract ConnectorDelayedAdmin is Auth { - ConnectorGateway public gateway; +contract DelayedAdmin is Auth { + Gateway public gateway; // --- Events --- event File(bytes32 indexed what, address indexed data); @@ -18,9 +18,9 @@ contract ConnectorDelayedAdmin is Auth { function file(bytes32 what, address data) external auth { if (what == "gateway") { - gateway = ConnectorGateway(data); + gateway = Gateway(data); } else { - revert("ConnectorAdmin/file-unrecognized-param"); + revert("Admin/file-unrecognized-param"); } emit File(what, data); } diff --git a/src/admin/PauseAdmin.sol b/src/admin/PauseAdmin.sol index bb4eb82d..b538fcbb 100644 --- a/src/admin/PauseAdmin.sol +++ b/src/admin/PauseAdmin.sol @@ -2,11 +2,11 @@ pragma solidity ^0.8.18; pragma abicoder v2; -import {ConnectorGateway} from "../routers/Gateway.sol"; +import {Gateway} from "../Gateway.sol"; import "./../auth/auth.sol"; -contract ConnectorPauseAdmin is Auth { - ConnectorGateway public gateway; +contract PauseAdmin is Auth { + Gateway public gateway; // --- Events --- @@ -19,9 +19,9 @@ contract ConnectorPauseAdmin is Auth { function file(bytes32 what, address data) external auth { if (what == "gateway") { - gateway = ConnectorGateway(data); + gateway = Gateway(data); } else { - revert("ConnectorAdmin/file-unrecognized-param"); + revert("Admin/file-unrecognized-param"); } emit File(what, data); } diff --git a/src/liquidityPool/Factory.sol b/src/liquidityPool/Factory.sol index 7ab91b20..1b9a6606 100644 --- a/src/liquidityPool/Factory.sol +++ b/src/liquidityPool/Factory.sol @@ -9,11 +9,11 @@ interface ImmutableCreate2Factory { } interface LiquidityPoolFactoryLike { - function newLiquidityPool(uint64 _poolId, bytes16 _trancheId, uint128 _currencyId, address _asset, address _connector, address _admin, address _memberlist, string memory _name, string memory _symbol, uint8 _decimals) external returns (address); + function newLiquidityPool(uint64 _poolId, bytes16 _trancheId, uint128 _currencyId, address _asset, address _investmentManager, address _admin, address _memberlist, string memory _name, string memory _symbol, uint8 _decimals) external returns (address); } contract LiquidityPoolFactory { - function newLiquidityPool(uint64 _poolId, bytes16 _trancheId, uint128 _currencyId, address _asset, address _connector, address _admin, address _memberlist, string memory _name, string memory _symbol, uint8 _decimals) + function newLiquidityPool(uint64 _poolId, bytes16 _trancheId, uint128 _currencyId, address _asset, address _investmentManager, address _admin, address _memberlist, string memory _name, string memory _symbol, uint8 _decimals) public returns (address) { @@ -28,31 +28,31 @@ contract LiquidityPoolFactory { // then the address remains deterministic. lPool.file("name", _name); lPool.file("symbol", _symbol); - lPool.file("connector", _connector); + lPool.file("investmentManager", _investmentManager); lPool.file("asset", _asset); lPool.file("memberlist", _memberlist); lPool.setPoolDetails(_poolId, _trancheId); lPool.deny(msg.sender); lPool.rely(_admin); - lPool.rely(_connector); // to be able to update tokenPrices + lPool.rely(_investmentManager); // to be able to update tokenPrices lPool.deny(address(this)); return address(lPool); } } interface MemberlistFactoryLike { - function newMemberlist(address _admin, address _connector) external returns (address); + function newMemberlist(address _admin, address _investmentManager) external returns (address); } contract MemberlistFactory { - function newMemberlist(address _admin, address _connector) public returns (address memberList) { + function newMemberlist(address _admin, address _investmentManager) public returns (address memberList) { Memberlist memberlist = new Memberlist(); memberlist.deny(msg.sender); memberlist.rely(_admin); - memberlist.rely(_connector); // connector is updating members + memberlist.rely(_investmentManager); memberlist.deny(address(this)); return (address(memberlist)); diff --git a/src/liquidityPool/LiquidityPool.sol b/src/liquidityPool/LiquidityPool.sol index 28ead250..dd2496ea 100644 --- a/src/liquidityPool/LiquidityPool.sol +++ b/src/liquidityPool/LiquidityPool.sol @@ -22,16 +22,19 @@ pragma solidity ^0.8.18; import "../token/restricted.sol"; -interface ConnectorLike { +interface InvestmentManagerLike { function processDeposit(address _receiver, uint256 _assets) external returns (uint256); function processMint(address _receiver, uint256 _shares) external returns (uint256); function processWithdraw(uint256 _assets, address _receiver, address _owner) external returns (uint256); + function processRedeem(uint256 _shares, address _receiver, address _owner) external returns (uint256); function maxDeposit(address _user, address _tranche) external view returns (uint256); function maxMint(address _user, address _tranche) external view returns (uint256); function maxWithdraw(address _user, address _tranche) external view returns (uint256); function maxRedeem(address _user, address _tranche) external view returns (uint256); function requestRedeem(uint256 _shares, address _receiver) external; function requestDeposit(uint256 _assets, address _receiver) external; + function collectInvest(uint64 poolId, bytes16 trancheId, address receiver, address currency) external; + function collectRedeem(uint64 poolId, bytes16 trancheId, address receiver, address currency) external; } @@ -39,7 +42,7 @@ interface ConnectorLike { /// @author ilinzweilin contract LiquidityPool is RestrictedToken { - ConnectorLike public connector; + InvestmentManagerLike public investmentManager; address public asset; // underlying stable ERC-20 stable currency @@ -56,9 +59,9 @@ contract LiquidityPool is RestrictedToken { constructor(uint8 _decimals) RestrictedToken(_decimals) {} - /// @dev connector and asset address to be filed by the factory on deployment + /// @dev investmentManager and asset address to be filed by the factory on deployment function file(bytes32 _what, address _data) override public auth { - if (_what == "connector") connector = ConnectorLike(_data); + if (_what == "investmentManager") investmentManager = InvestmentManagerLike(_data); else if (_what == "asset") asset = _data; else if (_what == "memberlist") memberlist = MemberlistLike(_data); else revert("LiquidityPool/file-unrecognized-param"); @@ -90,7 +93,7 @@ contract LiquidityPool is RestrictedToken { /// @return Maximum amount of stable currency that can be deposited into the Tranche by the receiver after the epoch had been executed on Centrifuge chain. function maxDeposit(address _receiver) public view returns (uint256) { - return connector.maxDeposit(_receiver, address(this)); + return investmentManager.maxDeposit(_receiver, address(this)); } /// @return shares that any user would get for an amount of assets provided -> convertToShares @@ -99,27 +102,28 @@ contract LiquidityPool is RestrictedToken { } /// @dev request asset deposit for a receiver to be included in the next epoch execution. Asset is locked in the escrow on request submission - function requestDeposit(uint256 _assets, address _receiver) auth public { - connector.requestDeposit(_assets, _receiver); + function requestDeposit(uint256 _assets) public { + investmentManager.requestDeposit(_assets, msg.sender); } /// @dev collect shares for deposited funds after pool epoch execution. maxMint is the max amount of shares that can be collected. Required assets must already be locked /// maxDeposit is the amount of funds that was successfully invested into the pool on Centrifuge chain - function deposit(uint256 _assets, address _receiver) auth public returns (uint256 shares) { - shares = connector.processDeposit( _receiver, _assets); + function deposit(uint256 _assets, address _receiver) public returns (uint256 shares) { + shares = investmentManager.processDeposit( _receiver, _assets); emit Deposit(address(this), _receiver, _assets, shares); } /// @dev collect shares for deposited funds after pool epoch execution. maxMint is the max amount of shares that can be collected. Required assets must already be locked /// maxDeposit is the amount of funds that was successfully invested into the pool on Centrifuge chain - function mint(uint256 _shares, address _receiver) auth public returns (uint256 assets) { - assets = connector.processMint(_receiver, _shares); + function mint(uint256 _shares, address _receiver) public returns (uint256 assets) { + // require(_receiver == msg.sender, "LiquidityPool/not-authorized-to-mint"); + assets = investmentManager.processMint(_receiver, _shares); emit Deposit(address(this), _receiver, assets, _shares); } /// @dev Maximum amount of shares that can be claimed by the receiver after the epoch has been executed on the Centrifuge chain side. function maxMint(address _receiver) external view returns (uint256 maxShares) { - maxShares = connector.maxMint(_receiver, address(this)); + maxShares = investmentManager.maxMint(_receiver, address(this)); } /// @return assets that any user would get for an amount of shares provided -> convertToAssets @@ -128,13 +132,13 @@ contract LiquidityPool is RestrictedToken { } /// @dev request share redemption for a receiver to be included in the next epoch execution. Shares are locked in the escrow on request submission - function requestRedeem(uint256 _shares, address _receiver) auth public { - connector.requestRedeem(_shares, _receiver); + function requestRedeem(uint256 _shares) public { + investmentManager.requestRedeem(_shares, msg.sender); } /// @return maxAssets that the receiver can withdraw function maxWithdraw(address _receiver) public view returns (uint256 maxAssets) { - return connector.maxWithdraw(_receiver, address(this)); + return investmentManager.maxWithdraw(_receiver, address(this)); } /// @return shares that a user would need to redeem in order to receive the given amount of assets -> convertToAssets @@ -144,15 +148,17 @@ contract LiquidityPool is RestrictedToken { /// @dev Withdraw assets after successful epoch execution. Receiver will receive an exact amount of _assets for a certain amount of shares that has been redeemed from Owner during epoch execution. /// @return shares that have been redeemed for the excat _assets amount - function withdraw(uint256 _assets, address _receiver, address _owner) auth public returns (uint256 shares) { - uint sharesRedeemed = connector.processWithdraw( _assets, _receiver, _owner); + function withdraw(uint256 _assets, address _receiver, address _owner) public returns (uint256 shares) { + // check if messgae sender can spend owners funds + require(_owner == msg.sender, "LiquidityPool/not-authorized-to-withdraw"); + uint sharesRedeemed = investmentManager.processWithdraw( _assets, _receiver, _owner); emit Withdraw(address(this), _receiver, _owner, _assets, sharesRedeemed); return sharesRedeemed; } /// @dev Max amount of shares that can be redeemed by the owner after redemption was requested function maxRedeem(address _owner) public view returns (uint256 maxShares) { - return connector.maxRedeem(_owner, address(this)); + return investmentManager.maxRedeem(_owner, address(this)); } /// @return assets that any user could redeem for an given amount of shares -> convertToAssets @@ -162,8 +168,9 @@ contract LiquidityPool is RestrictedToken { /// @dev Redeem shares after successful epoch execution. Receiver will receive assets for the exact amount of redeemed shares from Owner after epoch execution. /// @return assets currency payout for the exact amount of redeemed _shares - function redeem(uint256 _shares, address _receiver, address _owner) auth public returns (uint256 assets) { - uint currencyPayout = connector.processWithdraw(_shares, _receiver, _owner); + function redeem(uint256 _shares, address _receiver, address _owner) public returns (uint256 assets) { + require(_owner == msg.sender, "LiquidityPool/not-authorized-to-redeem"); + uint currencyPayout = investmentManager.processRedeem(_shares, _receiver, _owner); emit Withdraw(address(this), _receiver, _owner, currencyPayout, _shares); return currencyPayout; } @@ -173,4 +180,13 @@ contract LiquidityPool is RestrictedToken { latestPrice = _tokenPrice; lastPriceUpdate = block.timestamp; } + + function collectRedeem(address _receiver) public { + investmentManager.collectRedeem(poolId, trancheId, _receiver, asset); + } + + function collectInvest(address _receiver) public { + investmentManager.collectInvest(poolId, trancheId, _receiver, asset); + } } + diff --git a/src/routers/axelar/Router.sol b/src/routers/axelar/Router.sol index 7cd9f810..317651ab 100644 --- a/src/routers/axelar/Router.sol +++ b/src/routers/axelar/Router.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.18; pragma abicoder v2; -interface ConnectorLike { +interface InvestmentManagerLike { function addPool(uint64 poolId, uint128 currency, uint8 decimals) external; function addTranche( uint64 poolId, @@ -32,16 +32,16 @@ interface AxelarGatewayLike { external; } -interface ConnectorGatewayLike { +interface GatewayLike { function handle(bytes memory message) external; } -contract ConnectorAxelarRouter is AxelarExecutableLike { +contract AxelarRouter is AxelarExecutableLike { mapping(address => uint256) public wards; - ConnectorLike public immutable connector; + InvestmentManagerLike public immutable investmentManager; AxelarGatewayLike public immutable axelarGateway; - ConnectorGatewayLike public connectorGateway; + GatewayLike public gateway; string public constant axelarCentrifugeChainId = "Centrifuge"; string public constant axelarCentrifugeChainAddress = ""; @@ -51,15 +51,15 @@ contract ConnectorAxelarRouter is AxelarExecutableLike { event Deny(address indexed user); event File(bytes32 indexed what, address addr); - constructor(address connector_, address axelarGateway_) { - connector = ConnectorLike(connector_); + constructor(address investmentManager_, address axelarGateway_) { + investmentManager = InvestmentManagerLike(investmentManager_); axelarGateway = AxelarGatewayLike(axelarGateway_); wards[msg.sender] = 1; emit Rely(msg.sender); } modifier auth() { - require(wards[msg.sender] == 1, "ConnectorAxelarRouter/not-authorized"); + require(wards[msg.sender] == 1, "AxelarRouter/not-authorized"); _; } @@ -67,13 +67,13 @@ contract ConnectorAxelarRouter is AxelarExecutableLike { require( msg.sender == address(axelarGateway) && keccak256(bytes(axelarCentrifugeChainId)) == keccak256(bytes(sourceChain)), - "ConnectorAxelarRouter/invalid-origin" + "AxelarRouter/invalid-origin" ); _; } - modifier onlyConnector() { - require(msg.sender == address(connector), "ConnectorAxelarRouter/only-connector-allowed-to-call"); + modifier onlyInvestmentManager() { + require(msg.sender == address(investmentManager), "AxelarRouter/only-investmentManager-allowed-to-call"); _; } @@ -90,7 +90,7 @@ contract ConnectorAxelarRouter is AxelarExecutableLike { function file(bytes32 what, address gateway_) external auth { if (what == "gateway") { - connectorGateway = ConnectorGatewayLike(gateway_); + gateway = GatewayLike(gateway_); } else { revert("ConnectorXCMRouter/file-unrecognized-param"); } @@ -103,11 +103,11 @@ contract ConnectorAxelarRouter is AxelarExecutableLike { external onlyCentrifugeChainOrigin(sourceChain) { - connectorGateway.handle(payload); + gateway.handle(payload); } // --- Outgoing --- - function send(bytes memory message) public onlyConnector { + function send(bytes memory message) public onlyInvestmentManager { axelarGateway.callContract(axelarCentrifugeChainId, axelarCentrifugeChainAddress, message); } } diff --git a/src/routers/xcm/Router.sol b/src/routers/xcm/Router.sol index 6f858312..db21ec98 100644 --- a/src/routers/xcm/Router.sol +++ b/src/routers/xcm/Router.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.18; pragma abicoder v2; import {TypedMemView} from "memview-sol/TypedMemView.sol"; -import {ConnectorMessages} from "../../Messages.sol"; +import {Messages} from "../../Messages.sol"; struct Multilocation { uint8 parents; @@ -41,7 +41,7 @@ interface GatewayLike { function handle(bytes memory message) external; } -contract ConnectorXCMRouter { +contract XCMRouter { using TypedMemView for bytes; address constant XCM_TRANSACTOR_V2_ADDRESS = 0x000000000000000000000000000000000000080D; @@ -51,8 +51,8 @@ contract ConnectorXCMRouter { GatewayLike public gateway; address public immutable centrifugeChainOrigin; - uint8 public immutable centrifugeChainConnectorsPalletIndex; - uint8 public immutable centrifugeChainConnectorsPalletHandleIndex; + uint8 public immutable centrifugeChainLiquidityPoolsPalletIndex; + uint8 public immutable centrifugeChainLiquidityPoolsPalletHandleIndex; // --- Events --- event Rely(address indexed user); @@ -62,12 +62,12 @@ contract ConnectorXCMRouter { constructor( address centrifugeChainOrigin_, - uint8 centrifugeChainConnectorsPalletIndex_, - uint8 centrifugeChainConnectorsPalletHandleIndex_ + uint8 centrifugeChainLiquidityPoolsPalletIndex_, + uint8 centrifugeChainLiquidityPoolsPalletHandleIndex_ ) { centrifugeChainOrigin = centrifugeChainOrigin_; - centrifugeChainConnectorsPalletIndex = centrifugeChainConnectorsPalletIndex_; - centrifugeChainConnectorsPalletHandleIndex = centrifugeChainConnectorsPalletHandleIndex_; + centrifugeChainLiquidityPoolsPalletIndex = centrifugeChainLiquidityPoolsPalletIndex_; + centrifugeChainLiquidityPoolsPalletHandleIndex = centrifugeChainLiquidityPoolsPalletHandleIndex_; xcmWeightInfo = XcmWeightInfo({ buyExecutionWeightLimit: 19000000000, transactWeightAtMost: 8000000000, @@ -79,17 +79,17 @@ contract ConnectorXCMRouter { } modifier auth() { - require(wards[msg.sender] == 1, "ConnectorXCMRouter/not-authorized"); + require(wards[msg.sender] == 1, "XCMRouter/not-authorized"); _; } modifier onlyCentrifugeChainOrigin() { - require(msg.sender == address(centrifugeChainOrigin), "ConnectorXCMRouter/invalid-origin"); + require(msg.sender == address(centrifugeChainOrigin), "XCMRouter/invalid-origin"); _; } modifier onlyGateway() { - require(msg.sender == address(gateway), "ConnectorXCMRouter/only-gateway-allowed-to-call"); + require(msg.sender == address(gateway), "XCMRouter/only-gateway-allowed-to-call"); _; } @@ -108,7 +108,7 @@ contract ConnectorXCMRouter { if (what == "gateway") { gateway = GatewayLike(gateway_); } else { - revert("ConnectorXCMRouter/file-unrecognized-param"); + revert("XCMRouter/file-unrecognized-param"); } emit File(what, gateway_); @@ -159,9 +159,9 @@ contract ConnectorXCMRouter { function centrifuge_handle_call(bytes memory message) internal view returns (bytes memory) { return abi.encodePacked( // The centrifuge chain Connectors pallet index - centrifugeChainConnectorsPalletIndex, + centrifugeChainLiquidityPoolsPalletIndex, // The `handle` call index within the Connectors pallet - centrifugeChainConnectorsPalletHandleIndex, + centrifugeChainLiquidityPoolsPalletHandleIndex, // We need to specify the length of the message in the scale-encoding format message_length_scale_encoded(message), // The connector message itself @@ -174,25 +174,25 @@ contract ConnectorXCMRouter { function message_length_scale_encoded(bytes memory message) internal pure returns (bytes memory) { bytes29 _msg = message.ref(0); - if (ConnectorMessages.isTransfer(_msg)) { + if (Messages.isTransfer(_msg)) { return hex"8501"; - } else if (ConnectorMessages.isTransferTrancheTokens(_msg)) { + } else if (Messages.isTransferTrancheTokens(_msg)) { // A TransferTrancheTokens message is 82 bytes long which encodes to 0x4901 in Scale return hex"4901"; - } else if (ConnectorMessages.isIncreaseInvestOrder(_msg)) { + } else if (Messages.isIncreaseInvestOrder(_msg)) { return hex"6501"; - } else if (ConnectorMessages.isDecreaseInvestOrder(_msg)) { + } else if (Messages.isDecreaseInvestOrder(_msg)) { return hex"6501"; - } else if (ConnectorMessages.isIncreaseRedeemOrder(_msg)) { + } else if (Messages.isIncreaseRedeemOrder(_msg)) { return hex"6501"; - } else if (ConnectorMessages.isDecreaseRedeemOrder(_msg)) { + } else if (Messages.isDecreaseRedeemOrder(_msg)) { return hex"6501"; - } else if (ConnectorMessages.isCollectInvest(_msg)) { + } else if (Messages.isCollectInvest(_msg)) { return hex"e4"; - } else if (ConnectorMessages.isCollectRedeem(_msg)) { + } else if (Messages.isCollectRedeem(_msg)) { return hex"e4"; } else { - revert("ConnectorXCMRouter/unsupported-outgoing-message"); + revert("XCMRouter/unsupported-outgoing-message"); } } diff --git a/test/Admin.t.sol b/test/Admin.t.sol index 4d6e9c22..678c47e4 100644 --- a/test/Admin.t.sol +++ b/test/Admin.t.sol @@ -2,18 +2,18 @@ pragma solidity ^0.8.18; pragma abicoder v2; -import {CentrifugeConnector} from "src/Connector.sol"; -import {ConnectorGateway} from "src/routers/Gateway.sol"; -import {ConnectorEscrow} from "src/Escrow.sol"; -import {ConnectorPauseAdmin} from "src/admin/PauseAdmin.sol"; -import {ConnectorDelayedAdmin} from "src/admin/DelayedAdmin.sol"; +import {InvestmentManager} from "src/InvestmentManager.sol"; +import {Gateway} from "src/Gateway.sol"; +import {Escrow} from "src/Escrow.sol"; +import {PauseAdmin} from "src/admin/PauseAdmin.sol"; +import {DelayedAdmin} from "src/admin/DelayedAdmin.sol"; import {TrancheTokenFactory, MemberlistFactory} from "src/token/factory.sol"; import {RestrictedTokenLike} from "src/token/restricted.sol"; import {ERC20} from "src/token/erc20.sol"; import {MemberlistLike, Memberlist} from "src/token/memberlist.sol"; -import {MockHomeConnector} from "./mock/MockHomeConnector.sol"; +import {MockHomeLiquidityPools} from "./mock/MockHomeLiquidityPools.sol"; import {MockXcmRouter} from "./mock/MockXcmRouter.sol"; -import {ConnectorMessages} from "../src/Messages.sol"; +import {Messages} from "../src/Messages.sol"; import "forge-std/Test.sol"; interface EscrowLike_ { @@ -22,12 +22,12 @@ interface EscrowLike_ { } contract AdminTest is Test { - CentrifugeConnector connector; - ConnectorGateway gateway; - MockHomeConnector centChainConnector; + InvestmentManager investmentManager; + Gateway gateway; + MockHomeLiquidityPools centChainLiquidityPools; MockXcmRouter mockXcmRouter; - ConnectorPauseAdmin pauseAdmin; - ConnectorDelayedAdmin delayedAdmin; + PauseAdmin pauseAdmin; + DelayedAdmin delayedAdmin; uint256 shortWait; uint256 longWait; uint256 gracePeriod; @@ -36,27 +36,27 @@ contract AdminTest is Test { shortWait = 24 hours; longWait = 48 hours; gracePeriod = 48 hours; - address escrow_ = address(new ConnectorEscrow()); + address escrow_ = address(new Escrow()); address tokenFactory_ = address(new TrancheTokenFactory()); address memberlistFactory_ = address(new MemberlistFactory()); - connector = new CentrifugeConnector(escrow_, tokenFactory_, memberlistFactory_); + investmentManager = new InvestmentManager(escrow_, tokenFactory_, memberlistFactory_); - mockXcmRouter = new MockXcmRouter(address(connector)); + mockXcmRouter = new MockXcmRouter(address(investmentManager)); - centChainConnector = new MockHomeConnector(address(mockXcmRouter)); - pauseAdmin = new ConnectorPauseAdmin(); - delayedAdmin = new ConnectorDelayedAdmin(); - gateway = new ConnectorGateway(address(connector), address(mockXcmRouter), shortWait, longWait, gracePeriod); + centChainLiquidityPools = new MockHomeLiquidityPools(address(mockXcmRouter)); + pauseAdmin = new PauseAdmin(); + delayedAdmin = new DelayedAdmin(); + gateway = new Gateway(address(investmentManager), address(mockXcmRouter), shortWait, longWait, gracePeriod); gateway.rely(address(pauseAdmin)); gateway.rely(address(delayedAdmin)); - connector.file("gateway", address(gateway)); + investmentManager.file("gateway", address(gateway)); pauseAdmin.file("gateway", address(gateway)); delayedAdmin.file("gateway", address(gateway)); - EscrowLike_(escrow_).rely(address(connector)); + EscrowLike_(escrow_).rely(address(investmentManager)); mockXcmRouter.file("gateway", address(gateway)); - connector.rely(address(gateway)); + investmentManager.rely(address(gateway)); EscrowLike_(escrow_).rely(address(gateway)); } @@ -72,7 +72,7 @@ contract AdminTest is Test { function testPauseAuth(address usr) public { vm.assume(usr != address(this)); - vm.expectRevert("ConnectorAdmin/not-authorized"); + vm.expectRevert("not-authorized"); vm.prank(usr); pauseAdmin.pause(); } @@ -92,15 +92,15 @@ contract AdminTest is Test { vm.assume(recipient != address(0)); ERC20 erc20 = newErc20(tokenName, tokenSymbol, decimals); - centChainConnector.addCurrency(currency, address(erc20)); + centChainLiquidityPools.addCurrency(currency, address(erc20)); // First, an outgoing transfer must take place which has funds currency of the currency moved to // the escrow account, from which funds are moved from into the recipient on an incoming transfer. - erc20.approve(address(connector), type(uint256).max); + erc20.approve(address(investmentManager), type(uint256).max); erc20.mint(address(this), amount); pauseAdmin.pause(); - vm.expectRevert("ConnectorGateway/paused"); - connector.transfer(address(erc20), bytes32(bytes20(recipient)), amount); + vm.expectRevert("Gateway/paused"); + investmentManager.transfer(address(erc20), bytes32(bytes20(recipient)), amount); } function testIncomingTransferWhilePausedFails( @@ -118,18 +118,18 @@ contract AdminTest is Test { vm.assume(recipient != address(0)); ERC20 erc20 = newErc20(tokenName, tokenSymbol, decimals); - centChainConnector.addCurrency(currency, address(erc20)); + centChainLiquidityPools.addCurrency(currency, address(erc20)); // First, an outgoing transfer must take place which has funds currency of the currency moved to // the escrow account, from which funds are moved from into the recipient on an incoming transfer. - erc20.approve(address(connector), type(uint256).max); + erc20.approve(address(investmentManager), type(uint256).max); erc20.mint(address(this), amount); - connector.transfer(address(erc20), bytes32(bytes20(recipient)), amount); - assertEq(erc20.balanceOf(address(connector.escrow())), amount); + investmentManager.transfer(address(erc20), bytes32(bytes20(recipient)), amount); + assertEq(erc20.balanceOf(address(investmentManager.escrow())), amount); pauseAdmin.pause(); - vm.expectRevert("ConnectorGateway/paused"); - centChainConnector.incomingTransfer(currency, sender, bytes32(bytes20(recipient)), amount); + vm.expectRevert("Gateway/paused"); + centChainLiquidityPools.incomingTransfer(currency, sender, bytes32(bytes20(recipient)), amount); } function testUnpausingResumesFunctionality( @@ -144,24 +144,24 @@ contract AdminTest is Test { vm.assume(decimals > 0); vm.assume(amount > 0); vm.assume(currency != 0); - vm.assume(recipient != address(connector.escrow())); + vm.assume(recipient != address(investmentManager.escrow())); vm.assume(recipient != address(0)); ERC20 erc20 = newErc20(tokenName, tokenSymbol, decimals); vm.assume(recipient != address(erc20)); - centChainConnector.addCurrency(currency, address(erc20)); + centChainLiquidityPools.addCurrency(currency, address(erc20)); // First, an outgoing transfer must take place which has funds currency of the currency moved to // the escrow account, from which funds are moved from into the recipient on an incoming transfer. - erc20.approve(address(connector), type(uint256).max); + erc20.approve(address(investmentManager), type(uint256).max); erc20.mint(address(this), amount); pauseAdmin.pause(); pauseAdmin.unpause(); - connector.transfer(address(erc20), bytes32(bytes20(recipient)), amount); - assertEq(erc20.balanceOf(address(connector.escrow())), amount); + investmentManager.transfer(address(erc20), bytes32(bytes20(recipient)), amount); + assertEq(erc20.balanceOf(address(investmentManager.escrow())), amount); - centChainConnector.incomingTransfer(currency, sender, bytes32(bytes20(recipient)), amount); - assertEq(erc20.balanceOf(address(connector.escrow())), 0); + centChainLiquidityPools.incomingTransfer(currency, sender, bytes32(bytes20(recipient)), amount); + assertEq(erc20.balanceOf(address(investmentManager.escrow())), 0); assertEq(erc20.balanceOf(recipient), amount); } @@ -186,7 +186,7 @@ contract AdminTest is Test { address spell = vm.addr(1); delayedAdmin.schedule(spell); vm.warp(block.timestamp + longWait - 1 hours); - vm.expectRevert("ConnectorGateway/user-not-ready"); + vm.expectRevert("Gateway/user-not-ready"); gateway.executeScheduledRely(spell); } @@ -194,7 +194,7 @@ contract AdminTest is Test { address spell = vm.addr(1); delayedAdmin.schedule(spell); vm.warp(block.timestamp + longWait + gateway.gracePeriod()); - vm.expectRevert("ConnectorGateway/user-too-old"); + vm.expectRevert("Gateway/user-too-old"); gateway.executeScheduledRely(spell); } @@ -205,14 +205,14 @@ contract AdminTest is Test { delayedAdmin.cancelSchedule(spell); assertEq(gateway.relySchedule(spell), 0); vm.warp(block.timestamp + longWait + 1 hours); - vm.expectRevert("ConnectorGateway/user-not-scheduled"); + vm.expectRevert("Gateway/user-not-scheduled"); gateway.executeScheduledRely(spell); } function testUnauthorizedCancelFails() public { address spell = vm.addr(1); delayedAdmin.schedule(spell); - vm.expectRevert("ConnectorAdmin/not-authorized"); + vm.expectRevert("not-authorized"); vm.prank(spell); delayedAdmin.cancelSchedule(spell); } @@ -221,7 +221,7 @@ contract AdminTest is Test { function testShortRelyWorks() public { address spell = vm.addr(1); - centChainConnector.incomingScheduleRely(spell); + centChainLiquidityPools.incomingScheduleRely(spell); vm.warp(block.timestamp + shortWait + 1 hours); gateway.executeScheduledRely(spell); assertEq(gateway.wards(spell), 1); @@ -229,17 +229,17 @@ contract AdminTest is Test { function testShortRelyFailsBefore24hours() public { address spell = vm.addr(1); - centChainConnector.incomingScheduleRely(spell); + centChainLiquidityPools.incomingScheduleRely(spell); vm.warp(block.timestamp + shortWait - 1 hours); - vm.expectRevert("ConnectorGateway/user-not-ready"); + vm.expectRevert("Gateway/user-not-ready"); gateway.executeScheduledRely(spell); } function testShortRelyFailsAfterGracePeriod() public { address spell = vm.addr(1); - centChainConnector.incomingScheduleRely(spell); + centChainLiquidityPools.incomingScheduleRely(spell); vm.warp(block.timestamp + shortWait + gateway.gracePeriod()); - vm.expectRevert("ConnectorGateway/user-too-old"); + vm.expectRevert("Gateway/user-too-old"); gateway.executeScheduledRely(spell); } diff --git a/test/Connector.t.sol b/test/Connector.t.sol deleted file mode 100644 index f49a6545..00000000 --- a/test/Connector.t.sol +++ /dev/null @@ -1,804 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.18; -pragma abicoder v2; - -import { CentrifugeConnector, Tranche } from "../src/Connector.sol"; -import { ConnectorGateway } from "../src/routers/Gateway.sol"; -import { ConnectorEscrow } from "../src/Escrow.sol"; -import { LiquidityPoolFactory, MemberlistFactory } from "../src/liquidityPool/Factory.sol"; -import { LiquidityPool } from "../src/liquidityPool/LiquidityPool.sol"; -import { ERC20 } from "../src/token/erc20.sol"; - -import { MemberlistLike, Memberlist } from "../src/token/memberlist.sol"; -import { MockHomeConnector } from "./mock/MockHomeConnector.sol"; -import { MockXcmRouter } from "./mock/MockXcmRouter.sol"; -import { ConnectorMessages } from "../src/Messages.sol"; -import { ConnectorPauseAdmin } from "../src/admin/PauseAdmin.sol"; -import { ConnectorDelayedAdmin } from "../src/admin/DelayedAdmin.sol"; -import "forge-std/Test.sol"; -import "../src/Connector.sol"; - -interface EscrowLike_ { - function approve(address token, address spender, uint256 value) external; - function rely(address usr) external; -} - -interface AuthLike { - function wards(address user) external returns (uint); -} - -contract ConnectorTest is Test { - CentrifugeConnector evmConnector; - ConnectorGateway gateway; - MockHomeConnector connector; - MockXcmRouter mockXcmRouter; - - function setUp() public { - vm.chainId(1); - uint256 shortWait = 24 hours; - uint256 longWait = 48 hours; - uint256 gracePeriod = 48 hours; - address escrow_ = address(new ConnectorEscrow()); - address liquidityPoolFactory_ = address(new LiquidityPoolFactory()); - address memberlistFactory_ = address(new MemberlistFactory()); - - evmConnector = new CentrifugeConnector(escrow_, liquidityPoolFactory_, memberlistFactory_); - - mockXcmRouter = new MockXcmRouter(address(evmConnector)); - - connector = new MockHomeConnector(address(mockXcmRouter)); - ConnectorPauseAdmin pauseAdmin = new ConnectorPauseAdmin(); - ConnectorDelayedAdmin delayedAdmin = new ConnectorDelayedAdmin(); - - gateway = - new ConnectorGateway(address(evmConnector), address(mockXcmRouter), shortWait, longWait, gracePeriod); - gateway.rely(address(pauseAdmin)); - gateway.rely(address(delayedAdmin)); - pauseAdmin.file("gateway", address(gateway)); - delayedAdmin.file("gateway", address(gateway)); - evmConnector.file("gateway", address(gateway)); - EscrowLike_(escrow_).rely(address(evmConnector)); - mockXcmRouter.file("gateway", address(gateway)); - evmConnector.rely(address(gateway)); - ConnectorEscrow(escrow_).rely(address(gateway)); - } - - // function testConnectorDeactivationWorks() public { - // gateway.pause(); - // vm.expectRevert(bytes("CentrifugeConnector/connector-deactivated")); - // evmConnector.processDeposit(address(0), address(0), 0); - // } - - - function testAddCurrencyWorks(uint128 currency, uint128 badCurrency) public { - vm.assume(currency > 0); - vm.assume(badCurrency > 0); - vm.assume(currency != badCurrency); - - ERC20 erc20 = newErc20("X's Dollar", "USDX", 42); - connector.addCurrency(currency, address(erc20)); - (address address_) = evmConnector.currencyIdToAddress(currency); - assertEq(address_, address(erc20)); - - // Verify we can't override the same currency id another address - ERC20 badErc20 = newErc20("BadActor's Dollar", "BADUSD", 66); - vm.expectRevert(bytes("CentrifugeConnector/currency-id-in-use")); - connector.addCurrency(currency, address(badErc20)); - assertEq( evmConnector.currencyIdToAddress(currency), address(erc20)); - - // Verify we can't add a currency address that already exists associated with a different currency id - vm.expectRevert(bytes("CentrifugeConnector/currency-address-in-use")); - connector.addCurrency(badCurrency, address(erc20)); - assertEq( evmConnector.currencyIdToAddress(currency), address(erc20)); - } - - function testAddPoolWorks(uint64 poolId) public { - connector.addPool(poolId); - (uint64 actualPoolId,,) = evmConnector.pools(poolId); - assertEq(uint256(actualPoolId), uint256(poolId)); - } - - function testAllowPoolCurrencyWorks(uint128 currency, uint64 poolId) public { - vm.assume(currency > 0); - ERC20 token = newErc20("X's Dollar", "USDX", 42); - connector.addCurrency(currency, address(token)); - connector.addPool(poolId); - - connector.allowPoolCurrency(poolId, currency); - assertTrue(evmConnector.allowedPoolCurrencies(poolId, address(token))); - } - - function testAllowPoolCurrencyWithUnknownCurrencyFails(uint128 currency, uint64 poolId) public { - connector.addPool(poolId); - vm.expectRevert(bytes("CentrifugeConnector/unknown-currency")); - connector.allowPoolCurrency(poolId, currency); - } - - function testAddingPoolMultipleTimesFails(uint64 poolId) public { - connector.addPool(poolId); - - vm.expectRevert(bytes("CentrifugeConnector/pool-already-added")); - connector.addPool(poolId); - } - - function testAddingPoolAsNonRouterFails(uint64 poolId) public { - vm.expectRevert(bytes("CentrifugeConnector/not-the-gateway")); - evmConnector.addPool(poolId); - } - - function testAddingSingleTrancheWorks( - uint64 poolId, - bytes16 trancheId, - string memory tokenName, - string memory tokenSymbol, - uint8 decimals, - uint128 price - ) public { - connector.addPool(poolId); - (uint64 actualPoolId,,) = evmConnector.pools(poolId); - assertEq(uint256(actualPoolId), uint256(poolId)); - connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); - - ( - uint64 poolId_, - bytes16 trancheId_, - uint256 createdAt_, - string memory tokenName_, - string memory tokenSymbol_, - uint8 decimals_ - ) = evmConnector.tranches(poolId, trancheId); - - address[] memory liquidityPools_ = evmConnector.getLiquidityPoolsForTranche(poolId, trancheId); - - assertEq(poolId, poolId_); - assertEq(trancheId, trancheId_); - assertEq(block.timestamp, createdAt_); - assertEq( bytes32ToString(stringToBytes32(tokenName)), bytes32ToString(stringToBytes32(tokenName_))); - assertEq( bytes32ToString(stringToBytes32(tokenSymbol)), bytes32ToString(stringToBytes32(tokenSymbol_))); - assertEq(decimals, decimals_); - assertEq(liquidityPools_.length, 0); - } - - function testAddingTrancheMultipleTimesFails( - uint64 poolId, - uint8 decimals, - string memory tokenName, - string memory tokenSymbol, - bytes16 trancheId, - uint128 price - ) public { - connector.addPool(poolId); - connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); - - vm.expectRevert(bytes("CentrifugeConnector/tranche-already-exists")); - connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); - } - - function testAddingMultipleTranchesWorks( - uint64 poolId, - bytes16[] calldata trancheIds, - string memory tokenName, - string memory tokenSymbol, - uint8 decimals, - uint128 price - ) public { - vm.assume(trancheIds.length > 0 && trancheIds.length < 5); - vm.assume(!hasDuplicates(trancheIds)); - connector.addPool(poolId); - - for (uint256 i = 0; i < trancheIds.length; i++) { - connector.addTranche(poolId, trancheIds[i], tokenName, tokenSymbol, decimals, price); - ( - uint64 poolId_, - bytes16 trancheId_,,,, - ) = evmConnector.tranches(poolId, trancheIds[i]); - - assertEq(poolId, poolId_); - assertEq(trancheIds[i], trancheId_); - } - } - - function testAddingTranchesAsNonRouterFails( - uint64 poolId, - bytes16 trancheId, - string memory tokenName, - string memory tokenSymbol, - uint8 decimals, - uint128 price - ) public { - connector.addPool(poolId); - vm.expectRevert(bytes("CentrifugeConnector/not-the-gateway")); - evmConnector.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); - } - - function testAddingTranchesForNonExistentPoolFails( - uint64 poolId, - bytes16 trancheId, - string memory tokenName, - string memory tokenSymbol, - uint8 decimals, - uint128 price - ) public { - vm.expectRevert(bytes("CentrifugeConnector/invalid-pool")); - connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); - } - - function testDeployLiquidityPool( - uint64 poolId, - uint8 decimals, - string memory tokenName, - string memory tokenSymbol, - bytes16 trancheId, - uint128 price, - uint128 currency - ) public { - vm.assume(currency > 0); - ERC20 erc20 = newErc20("X's Dollar", "USDX", 42); - connector.addPool(poolId); // add pool - connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); // add tranche - - connector.addCurrency(currency, address(erc20)); - connector.allowPoolCurrency(poolId, currency); - - address lPoolAddress = evmConnector.deployLiquidityPool(poolId, trancheId, address(erc20)); - address lPool_ = evmConnector.liquidityPools(poolId, trancheId, address(erc20)); // make sure the pool was stored in connectors - address[] memory liquidityPools = evmConnector.getLiquidityPoolsForTranche(poolId, trancheId); - - // make sure the pool was added to the tranche struct - assertEq(lPoolAddress, lPool_); - bool lPoolIncluded; - for (uint i=0; i 0); - vm.assume(trancheId != wrongTrancheId); - - ERC20 erc20 = newErc20("X's Dollar", "USDX", 42); - connector.addPool(poolId); // add pool - connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); // add tranche - - connector.addCurrency(currency, address(erc20)); - connector.allowPoolCurrency(poolId, currency); - vm.expectRevert(bytes("CentrifugeConnector/tranche-does-not-exist")); - evmConnector.deployLiquidityPool(poolId, wrongTrancheId, address(erc20)); - } - - function testDeployingLiquidityPoolNonExistingPoolFails( - uint64 poolId, - uint8 decimals, - string memory tokenName, - string memory tokenSymbol, - bytes16 trancheId, - uint64 wrongPoolId, - uint128 price, - uint128 currency - ) public { - vm.assume(currency > 0); - vm.assume(poolId != wrongPoolId); - - ERC20 erc20 = newErc20("X's Dollar", "USDX", 42); - connector.addPool(poolId); // add pool - connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); // add tranche - - connector.addCurrency(currency, address(erc20)); - connector.allowPoolCurrency(poolId, currency); - vm.expectRevert(bytes("CentrifugeConnector/pool-does-not-exist")); - evmConnector.deployLiquidityPool(wrongPoolId, trancheId, address(erc20)); - } - - function testDeployingLiquidityPoolCurrencyNotSupportedFails( - uint64 poolId, - uint8 decimals, - string memory tokenName, - string memory tokenSymbol, - bytes16 trancheId, - uint128 price, - uint128 currency - ) public { - vm.assume(currency > 0); - - ERC20 erc20 = newErc20("X's Dollar", "USDX", 42); - connector.addPool(poolId); // add pool - connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); // add tranche - - connector.addCurrency(currency, address(erc20)); - - vm.expectRevert(bytes("CentrifugeConnector/pool-currency-not-allowed")); - evmConnector.deployLiquidityPool(poolId, trancheId, address(erc20)); - } - - function testDeployLiquidityPoolTwiceFails( - uint64 poolId, - uint8 decimals, - string memory tokenName, - string memory tokenSymbol, - bytes16 trancheId, - uint128 price, - uint128 currency - ) public { - vm.assume(currency > 0); - ERC20 erc20 = newErc20("X's Dollar", "USDX", 42); - connector.addPool(poolId); // add pool - connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); // add tranche - - connector.addCurrency(currency, address(erc20)); - connector.allowPoolCurrency(poolId, currency); - - evmConnector.deployLiquidityPool(poolId, trancheId, address(erc20)); - vm.expectRevert(bytes("CentrifugeConnector/liquidityPool-already-deployed")); - evmConnector.deployLiquidityPool(poolId, trancheId, address(erc20)); - - } - - function testUpdatingMemberWorks(uint64 poolId, uint8 decimals, uint128 currency, string memory tokenName, string memory tokenSymbol, bytes16 trancheId, address user, uint64 validUntil, uint128 price) - public - { - vm.assume(validUntil >= block.timestamp); - vm.assume(user != address(0)); - vm.assume(currency > 0); - ERC20 erc20 = newErc20("X's Dollar", "USDX", 42); - connector.addPool(poolId); // add pool - connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); // add tranche - connector.addCurrency(currency, address(erc20)); - connector.allowPoolCurrency(poolId, currency); - address lPool_ = evmConnector.deployLiquidityPool(poolId, trancheId, address(erc20)); - - connector.updateMember(poolId, trancheId, user, validUntil); - assertTrue(LiquidityPool(lPool_).hasMember(user)); - } - - function testUpdatingMemberAsNonRouterFails(uint64 poolId, uint128 currency, bytes16 trancheId, address user, uint64 validUntil) - public - { - vm.assume(validUntil >= block.timestamp); - vm.assume(user != address(0)); - vm.assume(currency > 0); - - vm.expectRevert(bytes("CentrifugeConnector/not-the-gateway")); - evmConnector.updateMember(poolId, trancheId, user, validUntil); - } - - function testUpdatingMemberForNonExistentPoolFails( - uint64 poolId, - bytes16 trancheId, - address user, - uint64 validUntil - ) public { - vm.assume(validUntil > block.timestamp); - evmConnector.file("gateway", address(this)); - vm.expectRevert(bytes("CentrifugeConnector/invalid-pool-or-tranche")); - evmConnector.updateMember(poolId, trancheId, user, validUntil); - } - - function testUpdatingMemberForNonExistentTrancheFails( - uint64 poolId, - bytes16 trancheId, - address user, - uint64 validUntil - ) public { - vm.assume(validUntil > block.timestamp); - connector.addPool(poolId); - - vm.expectRevert(bytes("CentrifugeConnector/invalid-pool-or-tranche")); - connector.updateMember(poolId, trancheId, user, validUntil); - } - - function testUpdatingTokenPriceWorks(uint64 poolId, uint8 decimals, uint128 currency, string memory tokenName, string memory tokenSymbol, bytes16 trancheId, uint128 price) public { - vm.assume(currency > 0); - ERC20 erc20 = newErc20("X's Dollar", "USDX", 42); - connector.addPool(poolId); // add pool - connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); // add tranche - connector.addCurrency(currency, address(erc20)); - connector.allowPoolCurrency(poolId, currency); - address lPool_ = evmConnector.deployLiquidityPool(poolId, trancheId, address(erc20)); - - connector.updateTokenPrice(poolId, trancheId, price); - assertEq(LiquidityPool(lPool_).latestPrice(), price); - assertEq(LiquidityPool(lPool_).lastPriceUpdate(), block.timestamp); - } - - function testUpdatingTokenPriceAsNonRouterFails(uint64 poolId, uint8 decimals, uint128 currency, string memory tokenName, string memory tokenSymbol, bytes16 trancheId, uint128 price) public { - vm.assume(currency > 0); - ERC20 erc20 = newErc20("X's Dollar", "USDX", 42); - connector.addPool(poolId); // add pool - connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); // add tranche - connector.addCurrency(currency, address(erc20)); - connector.allowPoolCurrency(poolId, currency); - evmConnector.deployLiquidityPool(poolId, trancheId, address(erc20)); - - vm.expectRevert(bytes("CentrifugeConnector/not-the-gateway")); - evmConnector.updateTokenPrice(poolId, trancheId, price); - } - - function testUpdatingTokenPriceForNonExistentPoolFails(uint64 poolId, bytes16 trancheId, uint128 price) public { - evmConnector.file("gateway", address(this)); - vm.expectRevert(bytes("CentrifugeConnector/invalid-pool-or-tranche")); - evmConnector.updateTokenPrice(poolId, trancheId, price); - } - - function testUpdatingTokenPriceForNonExistentTrancheFails(uint64 poolId, bytes16 trancheId, uint128 price) public { - connector.addPool(poolId); - - vm.expectRevert(bytes("CentrifugeConnector/invalid-pool-or-tranche")); - connector.updateTokenPrice(poolId, trancheId, price); - } - - function testIncomingTransferWithoutEscrowFundsFails( - string memory tokenName, - string memory tokenSymbol, - uint8 decimals, - uint128 currency, - bytes32 sender, - address recipient, - uint128 amount - ) public { - vm.assume(decimals > 0); - vm.assume(currency > 0); - vm.assume(amount > 0); - vm.assume(recipient != address(0)); - - ERC20 erc20 = newErc20(tokenName, tokenSymbol, decimals); - vm.assume(recipient != address(erc20)); - connector.addCurrency(currency, address(erc20)); - - assertEq(erc20.balanceOf(address(evmConnector.escrow())), 0); - vm.expectRevert(bytes("ERC20/insufficient-balance")); - connector.incomingTransfer(currency, sender, bytes32(bytes20(recipient)), amount); - assertEq(erc20.balanceOf(address(evmConnector.escrow())), 0); - assertEq(erc20.balanceOf(recipient), 0); - } - - function testIncomingTransferWorks( - string memory tokenName, - string memory tokenSymbol, - uint8 decimals, - uint128 currency, - bytes32 sender, - address recipient, - uint128 amount - ) public { - vm.assume(decimals > 0); - vm.assume(amount > 0); - vm.assume(currency != 0); - vm.assume(recipient != address(0)); - - ERC20 erc20 = newErc20(tokenName, tokenSymbol, decimals); - connector.addCurrency(currency, address(erc20)); - - // First, an outgoing transfer must take place which has funds currency of the currency moved to - // the escrow account, from which funds are moved from into the recipient on an incoming transfer. - erc20.approve(address(evmConnector), type(uint256).max); - erc20.mint(address(this), amount); - evmConnector.transfer(address(erc20), bytes32(bytes20(recipient)), amount); - assertEq(erc20.balanceOf(address(evmConnector.escrow())), amount); - - // Now we test the incoming message - connector.incomingTransfer(currency, sender, bytes32(bytes20(recipient)), amount); - assertEq(erc20.balanceOf(address(evmConnector.escrow())), 0); - assertEq(erc20.balanceOf(recipient), amount); - } - - // Verify that funds are moved from the msg.sender into the escrow account - function testOutgoingTransferWorks( - string memory tokenName, - string memory tokenSymbol, - uint8 decimals, - uint128 initialBalance, - uint128 currency, - bytes32 recipient, - uint128 amount - ) public { - vm.assume(decimals > 0); - vm.assume(amount > 0); - vm.assume(currency != 0); - vm.assume(initialBalance >= amount); - - ERC20 erc20 = newErc20(tokenName, tokenSymbol, decimals); - - vm.expectRevert(bytes("CentrifugeConnector/unknown-currency")); - evmConnector.transfer(address(erc20), recipient, amount); - connector.addCurrency(currency, address(erc20)); - - erc20.mint(address(this), initialBalance); - assertEq(erc20.balanceOf(address(this)), initialBalance); - assertEq(erc20.balanceOf(address(evmConnector.escrow())), 0); - erc20.approve(address(evmConnector), type(uint256).max); - - evmConnector.transfer(address(erc20), recipient, amount); - assertEq(erc20.balanceOf(address(this)), initialBalance - amount); - assertEq(erc20.balanceOf(address(evmConnector.escrow())), amount); - } - - function testTransferTrancheTokensToCentrifuge( - uint64 validUntil, - bytes32 centChainAddress, - uint128 amount, - uint64 poolId, - uint8 decimals, - string memory tokenName, - string memory tokenSymbol, - bytes16 trancheId, - uint128 currency - ) public { - vm.assume(currency > 0); - vm.assume(validUntil > block.timestamp + 7 days); - - address lPool_ = deployLiquidityPool(poolId, decimals, tokenName, tokenSymbol, trancheId, currency); - connector.updateMember(poolId, trancheId, address(this), validUntil); - - // fund this account with amount - connector.incomingTransferTrancheTokens(poolId, trancheId, uint64(block.chainid), currency, address(this), amount); - - // Verify the address(this) has the expected amount - assertEq(LiquidityPool(lPool_).balanceOf(address(this)), amount); - - // Now send the transfer from EVM -> Cent Chain - LiquidityPool(lPool_).approve(address(evmConnector), amount); - evmConnector.transferTrancheTokensToCentrifuge(poolId, trancheId, LiquidityPool(lPool_).asset(), centChainAddress, amount); - assertEq(LiquidityPool(lPool_).balanceOf(address(this)), 0); - - // Finally, verify the connector called `router.send` - bytes memory message = ConnectorMessages.formatTransferTrancheTokens( - poolId, - trancheId, - bytes32(bytes20(address(this))), - ConnectorMessages.formatDomain(ConnectorMessages.Domain.Centrifuge), - currency, - centChainAddress, - amount - ); - assertEq(mockXcmRouter.sentMessages(message), true); - } - - function testTransferTrancheTokensFromCentrifuge( - uint64 poolId, - uint8 decimals, - string memory tokenName, - string memory tokenSymbol, - bytes16 trancheId, - uint128 currency, - uint64 validUntil, - - address destinationAddress, - uint128 amount - ) public { - vm.assume(validUntil >= block.timestamp); - vm.assume(destinationAddress != address(0)); - vm.assume(currency > 0); - - address lPool_ = deployLiquidityPool(poolId, decimals, tokenName, tokenSymbol, trancheId, currency); - - connector.updateMember(poolId, trancheId, destinationAddress, validUntil); - assertTrue(LiquidityPool(lPool_).hasMember(destinationAddress)); - connector.incomingTransferTrancheTokens(poolId, trancheId, uint64(block.chainid), currency, destinationAddress, amount); - assertEq(LiquidityPoolLike(lPool_).balanceOf(destinationAddress), amount); - } - - function testTransferTrancheTokensFromCentrifugeWithoutMemberFails( - uint64 poolId, - uint8 decimals, - string memory tokenName, - string memory tokenSymbol, - bytes16 trancheId, - uint128 currency, - address destinationAddress, - uint128 amount - ) public { - vm.assume(destinationAddress != address(0)); - vm.assume(currency > 0); - - deployLiquidityPool(poolId, decimals, tokenName, tokenSymbol, trancheId, currency); - - vm.expectRevert(bytes("CentrifugeConnector/not-a-member")); - connector.incomingTransferTrancheTokens(poolId, trancheId, uint64(block.chainid), currency, destinationAddress, amount); - } - - function testTransferTrancheTokensToEVM( - uint64 poolId, - bytes16 trancheId, - string memory tokenName, - string memory tokenSymbol, - uint8 decimals, - uint64 validUntil, - address destinationAddress, - uint128 amount, - uint128 currency - ) public { - vm.assume(validUntil > block.timestamp + 7 days); - vm.assume(destinationAddress != address(0)); - vm.assume(currency > 0); - vm.assume(amount > 0); - - address lPool_ = deployLiquidityPool(poolId, decimals, tokenName, tokenSymbol, trancheId, currency); - connector.updateMember(poolId, trancheId, destinationAddress, validUntil); - connector.updateMember(poolId, trancheId, address(this), validUntil); - - // Fund this address with amount - connector.incomingTransferTrancheTokens(poolId, trancheId, uint64(block.chainid), currency, address(this), amount); - assertEq(LiquidityPool(lPool_).balanceOf(address(this)), amount); - - // Approve and transfer amount from this address to destinationAddress - LiquidityPool(lPool_).approve(address(evmConnector), amount); - evmConnector.transferTrancheTokensToEVM( - poolId, trancheId, LiquidityPool(lPool_).asset(), uint64(block.chainid), destinationAddress, amount - ); - assertEq(LiquidityPool(lPool_).balanceOf(address(this)), 0); - } - - - function testCollectRedeem( - uint64 poolId, - bytes16 trancheId, - string memory tokenName, - string memory tokenSymbol, - uint8 decimals, - uint128 currency, - uint8 trancheDecimals, - uint64 validUntil, - uint128 amount - ) public { - vm.assume(amount > 0); - vm.assume(currency > 0); - vm.assume(trancheDecimals > 0); - vm.assume(validUntil > block.timestamp + 7 days); - - ERC20 erc20 = newErc20("X's Dollar", "USDX", 42); - connector.addPool(poolId); // add pool - connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, 0); // add tranche - connector.addCurrency(currency, address(erc20)); - connector.allowPoolCurrency(poolId, currency); - - vm.expectRevert(bytes("CentrifugeConnector/unknown-tranche-token")); - evmConnector.collectRedeem(poolId, trancheId, address(erc20)); - - address lPool_ = evmConnector.deployLiquidityPool(poolId, trancheId, address(erc20)); - - vm.expectRevert(bytes("CentrifugeConnector/not-a-member")); - evmConnector.collectRedeem(poolId, trancheId, LiquidityPool(lPool_).asset()); - connector.updateMember(poolId, trancheId, address(this), validUntil); - - evmConnector.collectRedeem(poolId, trancheId, LiquidityPool(lPool_).asset()); - } - - function testCollectInvest( - uint64 poolId, - bytes16 trancheId, - string memory tokenName, - string memory tokenSymbol, - uint8 decimals, - uint128 currency, - uint64 validUntil, - uint128 amount - ) public { - vm.assume(amount > 0); - vm.assume(currency > 0); - vm.assume(decimals > 0); - vm.assume(validUntil > block.timestamp + 7 days); - - ERC20 erc20 = newErc20("X's Dollar", "USDX", 42); - connector.addPool(poolId); // add pool - connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, 0); // add tranche - connector.addCurrency(currency, address(erc20)); - connector.allowPoolCurrency(poolId, currency); - - vm.expectRevert(bytes("CentrifugeConnector/unknown-tranche-token")); - evmConnector.collectInvest(poolId, trancheId, address(erc20)); - - address lPool_ = evmConnector.deployLiquidityPool(poolId, trancheId, address(erc20)); - - vm.expectRevert(bytes("CentrifugeConnector/not-a-member")); - evmConnector.collectInvest(poolId, trancheId, LiquidityPool(lPool_).asset()); - connector.updateMember(poolId, trancheId, address(this), validUntil); - - evmConnector.collectInvest(poolId, trancheId, LiquidityPool(lPool_).asset()); - } - - // helpers - function deployLiquidityPool( - uint64 poolId, - uint8 decimals, - string memory tokenName, - string memory tokenSymbol, - bytes16 trancheId, - uint128 currency) internal returns (address lPool) { - // deploy liquidityPool - ERC20 erc20 = newErc20("X's Dollar", "USDX", 42); - connector.addPool(poolId); // add pool - connector.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, 0); // add tranche - connector.addCurrency(currency, address(erc20)); - connector.allowPoolCurrency(poolId, currency); - - lPool = evmConnector.deployLiquidityPool(poolId, trancheId, address(erc20)); - } - - function newErc20(string memory name, string memory symbol, uint8 decimals) internal returns (ERC20) { - ERC20 erc20 = new ERC20(decimals); - erc20.file("name", name); - erc20.file("symbol", symbol); - - return erc20; - } - - function stringToBytes32(string memory source) internal pure returns (bytes32 result) { - bytes memory tempEmptyStringTest = bytes(source); - if (tempEmptyStringTest.length == 0) { - return 0x0; - } - - assembly { - result := mload(add(source, 32)) - } - } - - function bytes32ToString(bytes32 _bytes32) internal pure returns (string memory) { - uint8 i = 0; - while (i < 32 && _bytes32[i] != 0) { - i++; - } - - bytes memory bytesArray = new bytes(i); - for (i = 0; i < 32 && _bytes32[i] != 0; i++) { - bytesArray[i] = _bytes32[i]; - } - return string(bytesArray); - } - - function toBytes32(bytes memory f) internal pure returns (bytes16 fc) { - assembly { - fc := mload(add(f, 32)) - } - return fc; - } - - function toBytes29(bytes memory f) internal pure returns (bytes29 fc) { - assembly { - fc := mload(add(f, 29)) - } - return fc; - } - - function hasDuplicates(bytes16[] calldata array) internal pure returns (bool) { - uint256 length = array.length; - for (uint256 i = 0; i < length; i++) { - for (uint256 j = i + 1; j < length; j++) { - if (array[i] == array[j]) { - return true; - } - } - } - return false; - } -} \ No newline at end of file diff --git a/test/InvestmentManager.t.sol b/test/InvestmentManager.t.sol new file mode 100644 index 00000000..e548820f --- /dev/null +++ b/test/InvestmentManager.t.sol @@ -0,0 +1,742 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.18; +pragma abicoder v2; + +import { InvestmentManager, Tranche } from "../src/InvestmentManager.sol"; +import { Gateway } from "../src/Gateway.sol"; +import { Escrow } from "../src/Escrow.sol"; +import { LiquidityPoolFactory, MemberlistFactory } from "../src/liquidityPool/Factory.sol"; +import { LiquidityPool } from "../src/liquidityPool/LiquidityPool.sol"; +import { ERC20 } from "../src/token/erc20.sol"; + +import { MemberlistLike, Memberlist } from "../src/token/memberlist.sol"; +import { MockHomeLiquidityPools } from "./mock/MockHomeLiquidityPools.sol"; +import { MockXcmRouter } from "./mock/MockXcmRouter.sol"; +import { Messages } from "../src/Messages.sol"; +import { PauseAdmin } from "../src/admin/PauseAdmin.sol"; +import { DelayedAdmin } from "../src/admin/DelayedAdmin.sol"; +import "forge-std/Test.sol"; +import "../src/InvestmentManager.sol"; + +interface EscrowLike_ { + function approve(address token, address spender, uint256 value) external; + function rely(address usr) external; +} + +interface AuthLike { + function wards(address user) external returns (uint); +} + +contract InvestmentManagerTest is Test { + InvestmentManager evmInvestmentManager; + Gateway gateway; + MockHomeLiquidityPools homePools; + MockXcmRouter mockXcmRouter; + + function setUp() public { + vm.chainId(1); + uint256 shortWait = 24 hours; + uint256 longWait = 48 hours; + uint256 gracePeriod = 48 hours; + address escrow_ = address(new Escrow()); + address liquidityPoolFactory_ = address(new LiquidityPoolFactory()); + address memberlistFactory_ = address(new MemberlistFactory()); + + evmInvestmentManager = new InvestmentManager(escrow_, liquidityPoolFactory_, memberlistFactory_); + + mockXcmRouter = new MockXcmRouter(address(evmInvestmentManager)); + + homePools = new MockHomeLiquidityPools(address(mockXcmRouter)); + PauseAdmin pauseAdmin = new PauseAdmin(); + DelayedAdmin delayedAdmin = new DelayedAdmin(); + + gateway = + new Gateway(address(evmInvestmentManager), address(mockXcmRouter), shortWait, longWait, gracePeriod); + gateway.rely(address(pauseAdmin)); + gateway.rely(address(delayedAdmin)); + pauseAdmin.file("gateway", address(gateway)); + delayedAdmin.file("gateway", address(gateway)); + evmInvestmentManager.file("gateway", address(gateway)); + EscrowLike_(escrow_).rely(address(evmInvestmentManager)); + mockXcmRouter.file("gateway", address(gateway)); + evmInvestmentManager.rely(address(gateway)); + Escrow(escrow_).rely(address(gateway)); + } + + // function testConnectorDeactivationWorks() public { + // gateway.pause(); + // vm.expectRevert(bytes("InvestmentManager/connector-deactivated")); + // evmInvestmentManager.processDeposit(address(0), address(0), 0); + // } + + + function testAddCurrencyWorks(uint128 currency, uint128 badCurrency) public { + vm.assume(currency > 0); + vm.assume(badCurrency > 0); + vm.assume(currency != badCurrency); + + ERC20 erc20 = newErc20("X's Dollar", "USDX", 42); + homePools.addCurrency(currency, address(erc20)); + (address address_) = evmInvestmentManager.currencyIdToAddress(currency); + assertEq(address_, address(erc20)); + + // Verify we can't override the same currency id another address + ERC20 badErc20 = newErc20("BadActor's Dollar", "BADUSD", 66); + vm.expectRevert(bytes("InvestmentManager/currency-id-in-use")); + homePools.addCurrency(currency, address(badErc20)); + assertEq( evmInvestmentManager.currencyIdToAddress(currency), address(erc20)); + + // Verify we can't add a currency address that already exists associated with a different currency id + vm.expectRevert(bytes("InvestmentManager/currency-address-in-use")); + homePools.addCurrency(badCurrency, address(erc20)); + assertEq( evmInvestmentManager.currencyIdToAddress(currency), address(erc20)); + } + + function testAddPoolWorks(uint64 poolId) public { + homePools.addPool(poolId); + (uint64 actualPoolId,,) = evmInvestmentManager.pools(poolId); + assertEq(uint256(actualPoolId), uint256(poolId)); + } + + function testAllowPoolCurrencyWorks(uint128 currency, uint64 poolId) public { + vm.assume(currency > 0); + ERC20 token = newErc20("X's Dollar", "USDX", 42); + homePools.addCurrency(currency, address(token)); + homePools.addPool(poolId); + + homePools.allowPoolCurrency(poolId, currency); + assertTrue(evmInvestmentManager.allowedPoolCurrencies(poolId, address(token))); + } + + function testAllowPoolCurrencyWithUnknownCurrencyFails(uint128 currency, uint64 poolId) public { + homePools.addPool(poolId); + vm.expectRevert(bytes("InvestmentManager/unknown-currency")); + homePools.allowPoolCurrency(poolId, currency); + } + + function testAddingPoolMultipleTimesFails(uint64 poolId) public { + homePools.addPool(poolId); + + vm.expectRevert(bytes("InvestmentManager/pool-already-added")); + homePools.addPool(poolId); + } + + function testAddingPoolAsNonRouterFails(uint64 poolId) public { + vm.expectRevert(bytes("InvestmentManager/not-the-gateway")); + evmInvestmentManager.addPool(poolId); + } + + function testAddingSingleTrancheWorks( + uint64 poolId, + bytes16 trancheId, + string memory tokenName, + string memory tokenSymbol, + uint8 decimals, + uint128 price + ) public { + homePools.addPool(poolId); + (uint64 actualPoolId,,) = evmInvestmentManager.pools(poolId); + assertEq(uint256(actualPoolId), uint256(poolId)); + homePools.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); + + ( + uint64 poolId_, + bytes16 trancheId_, + uint8 decimals_, + uint256 createdAt_, + string memory tokenName_, + string memory tokenSymbol_ + ) = evmInvestmentManager.tranches(poolId, trancheId); + + + address[] memory liquidityPools_ = evmInvestmentManager.getLiquidityPoolsForTranche(poolId, trancheId); + + assertEq(poolId, poolId_); + assertEq(trancheId, trancheId_); + assertEq(block.timestamp, createdAt_); + assertEq( bytes32ToString(stringToBytes32(tokenName)), bytes32ToString(stringToBytes32(tokenName_))); + assertEq( bytes32ToString(stringToBytes32(tokenSymbol)), bytes32ToString(stringToBytes32(tokenSymbol_))); + assertEq(decimals, decimals_); + assertEq(liquidityPools_.length, 0); + } + + function testAddingTrancheMultipleTimesFails( + uint64 poolId, + uint8 decimals, + string memory tokenName, + string memory tokenSymbol, + bytes16 trancheId, + uint128 price + ) public { + homePools.addPool(poolId); + homePools.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); + + vm.expectRevert(bytes("InvestmentManager/tranche-already-exists")); + homePools.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); + } + + function testAddingMultipleTranchesWorks( + uint64 poolId, + bytes16[] calldata trancheIds, + string memory tokenName, + string memory tokenSymbol, + uint8 decimals, + uint128 price + ) public { + vm.assume(trancheIds.length > 0 && trancheIds.length < 5); + vm.assume(!hasDuplicates(trancheIds)); + homePools.addPool(poolId); + + for (uint256 i = 0; i < trancheIds.length; i++) { + homePools.addTranche(poolId, trancheIds[i], tokenName, tokenSymbol, decimals, price); + ( + uint64 poolId_, + bytes16 trancheId_,,,, + ) = evmInvestmentManager.tranches(poolId, trancheIds[i]); + + assertEq(poolId, poolId_); + assertEq(trancheIds[i], trancheId_); + } + } + + function testAddingTranchesAsNonRouterFails( + uint64 poolId, + bytes16 trancheId, + string memory tokenName, + string memory tokenSymbol, + uint8 decimals, + uint128 price + ) public { + homePools.addPool(poolId); + vm.expectRevert(bytes("InvestmentManager/not-the-gateway")); + evmInvestmentManager.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); + } + + function testAddingTranchesForNonExistentPoolFails( + uint64 poolId, + bytes16 trancheId, + string memory tokenName, + string memory tokenSymbol, + uint8 decimals, + uint128 price + ) public { + vm.expectRevert(bytes("InvestmentManager/invalid-pool")); + homePools.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); + } + + function testDeployLiquidityPool( + uint64 poolId, + uint8 decimals, + string memory tokenName, + string memory tokenSymbol, + bytes16 trancheId, + uint128 price, + uint128 currency + ) public { + vm.assume(currency > 0); + ERC20 erc20 = newErc20("X's Dollar", "USDX", 42); + homePools.addPool(poolId); // add pool + homePools.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); // add tranche + + homePools.addCurrency(currency, address(erc20)); + homePools.allowPoolCurrency(poolId, currency); + + address lPoolAddress = evmInvestmentManager.deployLiquidityPool(poolId, trancheId, address(erc20)); + address lPool_ = evmInvestmentManager.liquidityPools(poolId, trancheId, address(erc20)); // make sure the pool was stored in connectors + address[] memory liquidityPools = evmInvestmentManager.getLiquidityPoolsForTranche(poolId, trancheId); + + // make sure the pool was added to the tranche struct + assertEq(lPoolAddress, lPool_); + bool lPoolIncluded; + for (uint i=0; i 0); + vm.assume(trancheId != wrongTrancheId); + + ERC20 erc20 = newErc20("X's Dollar", "USDX", 42); + homePools.addPool(poolId); // add pool + homePools.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); // add tranche + + homePools.addCurrency(currency, address(erc20)); + homePools.allowPoolCurrency(poolId, currency); + vm.expectRevert(bytes("InvestmentManager/tranche-does-not-exist")); + evmInvestmentManager.deployLiquidityPool(poolId, wrongTrancheId, address(erc20)); + } + + function testDeployingLiquidityPoolNonExistingPoolFails( + uint64 poolId, + uint8 decimals, + string memory tokenName, + string memory tokenSymbol, + bytes16 trancheId, + uint64 wrongPoolId, + uint128 price, + uint128 currency + ) public { + vm.assume(currency > 0); + vm.assume(poolId != wrongPoolId); + + ERC20 erc20 = newErc20("X's Dollar", "USDX", 42); + homePools.addPool(poolId); // add pool + homePools.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); // add tranche + + homePools.addCurrency(currency, address(erc20)); + homePools.allowPoolCurrency(poolId, currency); + vm.expectRevert(bytes("InvestmentManager/pool-does-not-exist")); + evmInvestmentManager.deployLiquidityPool(wrongPoolId, trancheId, address(erc20)); + } + + function testDeployingLiquidityPoolCurrencyNotSupportedFails( + uint64 poolId, + uint8 decimals, + string memory tokenName, + string memory tokenSymbol, + bytes16 trancheId, + uint128 price, + uint128 currency + ) public { + vm.assume(currency > 0); + + ERC20 erc20 = newErc20("X's Dollar", "USDX", 42); + homePools.addPool(poolId); // add pool + homePools.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); // add tranche + + homePools.addCurrency(currency, address(erc20)); + + vm.expectRevert(bytes("InvestmentManager/pool-currency-not-allowed")); + evmInvestmentManager.deployLiquidityPool(poolId, trancheId, address(erc20)); + } + + function testDeployLiquidityPoolTwiceFails( + uint64 poolId, + uint8 decimals, + string memory tokenName, + string memory tokenSymbol, + bytes16 trancheId, + uint128 price, + uint128 currency + ) public { + vm.assume(currency > 0); + ERC20 erc20 = newErc20("X's Dollar", "USDX", 42); + homePools.addPool(poolId); // add pool + homePools.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); // add tranche + + homePools.addCurrency(currency, address(erc20)); + homePools.allowPoolCurrency(poolId, currency); + + evmInvestmentManager.deployLiquidityPool(poolId, trancheId, address(erc20)); + vm.expectRevert(bytes("InvestmentManager/liquidityPool-already-deployed")); + evmInvestmentManager.deployLiquidityPool(poolId, trancheId, address(erc20)); + + } + + function testUpdatingMemberWorks(uint64 poolId, uint8 decimals, uint128 currency, string memory tokenName, string memory tokenSymbol, bytes16 trancheId, address user, uint64 validUntil, uint128 price) + public + { + vm.assume(validUntil >= block.timestamp); + vm.assume(user != address(0)); + vm.assume(currency > 0); + ERC20 erc20 = newErc20("X's Dollar", "USDX", 42); + homePools.addPool(poolId); // add pool + homePools.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); // add tranche + homePools.addCurrency(currency, address(erc20)); + homePools.allowPoolCurrency(poolId, currency); + address lPool_ = evmInvestmentManager.deployLiquidityPool(poolId, trancheId, address(erc20)); + + homePools.updateMember(poolId, trancheId, user, validUntil); + assertTrue(LiquidityPool(lPool_).hasMember(user)); + } + + function testUpdatingMemberAsNonRouterFails(uint64 poolId, uint128 currency, bytes16 trancheId, address user, uint64 validUntil) + public + { + vm.assume(validUntil >= block.timestamp); + vm.assume(user != address(0)); + vm.assume(currency > 0); + + vm.expectRevert(bytes("InvestmentManager/not-the-gateway")); + evmInvestmentManager.updateMember(poolId, trancheId, user, validUntil); + } + + function testUpdatingMemberForNonExistentPoolFails( + uint64 poolId, + bytes16 trancheId, + address user, + uint64 validUntil + ) public { + vm.assume(validUntil > block.timestamp); + evmInvestmentManager.file("gateway", address(this)); + vm.expectRevert(bytes("InvestmentManager/invalid-pool-or-tranche")); + evmInvestmentManager.updateMember(poolId, trancheId, user, validUntil); + } + + function testUpdatingMemberForNonExistentTrancheFails( + uint64 poolId, + bytes16 trancheId, + address user, + uint64 validUntil + ) public { + vm.assume(validUntil > block.timestamp); + homePools.addPool(poolId); + + vm.expectRevert(bytes("InvestmentManager/invalid-pool-or-tranche")); + homePools.updateMember(poolId, trancheId, user, validUntil); + } + + function testUpdatingTokenPriceWorks(uint64 poolId, uint8 decimals, uint128 currency, string memory tokenName, string memory tokenSymbol, bytes16 trancheId, uint128 price) public { + vm.assume(currency > 0); + ERC20 erc20 = newErc20("X's Dollar", "USDX", 42); + homePools.addPool(poolId); // add pool + homePools.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); // add tranche + homePools.addCurrency(currency, address(erc20)); + homePools.allowPoolCurrency(poolId, currency); + address lPool_ = evmInvestmentManager.deployLiquidityPool(poolId, trancheId, address(erc20)); + + homePools.updateTokenPrice(poolId, trancheId, price); + assertEq(LiquidityPool(lPool_).latestPrice(), price); + assertEq(LiquidityPool(lPool_).lastPriceUpdate(), block.timestamp); + } + + function testUpdatingTokenPriceAsNonRouterFails(uint64 poolId, uint8 decimals, uint128 currency, string memory tokenName, string memory tokenSymbol, bytes16 trancheId, uint128 price) public { + vm.assume(currency > 0); + ERC20 erc20 = newErc20("X's Dollar", "USDX", 42); + homePools.addPool(poolId); // add pool + homePools.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); // add tranche + homePools.addCurrency(currency, address(erc20)); + homePools.allowPoolCurrency(poolId, currency); + evmInvestmentManager.deployLiquidityPool(poolId, trancheId, address(erc20)); + + vm.expectRevert(bytes("InvestmentManager/not-the-gateway")); + evmInvestmentManager.updateTokenPrice(poolId, trancheId, price); + } + + function testUpdatingTokenPriceForNonExistentPoolFails(uint64 poolId, bytes16 trancheId, uint128 price) public { + evmInvestmentManager.file("gateway", address(this)); + vm.expectRevert(bytes("InvestmentManager/invalid-pool-or-tranche")); + evmInvestmentManager.updateTokenPrice(poolId, trancheId, price); + } + + function testUpdatingTokenPriceForNonExistentTrancheFails(uint64 poolId, bytes16 trancheId, uint128 price) public { + homePools.addPool(poolId); + + vm.expectRevert(bytes("InvestmentManager/invalid-pool-or-tranche")); + homePools.updateTokenPrice(poolId, trancheId, price); + } + + function testIncomingTransferWithoutEscrowFundsFails( + string memory tokenName, + string memory tokenSymbol, + uint8 decimals, + uint128 currency, + bytes32 sender, + address recipient, + uint128 amount + ) public { + vm.assume(decimals > 0); + vm.assume(currency > 0); + vm.assume(amount > 0); + vm.assume(recipient != address(0)); + + ERC20 erc20 = newErc20(tokenName, tokenSymbol, decimals); + vm.assume(recipient != address(erc20)); + homePools.addCurrency(currency, address(erc20)); + + assertEq(erc20.balanceOf(address(evmInvestmentManager.escrow())), 0); + vm.expectRevert(bytes("ERC20/insufficient-balance")); + homePools.incomingTransfer(currency, sender, bytes32(bytes20(recipient)), amount); + assertEq(erc20.balanceOf(address(evmInvestmentManager.escrow())), 0); + assertEq(erc20.balanceOf(recipient), 0); + } + + function testIncomingTransferWorks( + string memory tokenName, + string memory tokenSymbol, + uint8 decimals, + uint128 currency, + bytes32 sender, + address recipient, + uint128 amount + ) public { + vm.assume(decimals > 0); + vm.assume(amount > 0); + vm.assume(currency != 0); + vm.assume(recipient != address(0)); + + ERC20 erc20 = newErc20(tokenName, tokenSymbol, decimals); + homePools.addCurrency(currency, address(erc20)); + + // First, an outgoing transfer must take place which has funds currency of the currency moved to + // the escrow account, from which funds are moved from into the recipient on an incoming transfer. + erc20.approve(address(evmInvestmentManager), type(uint256).max); + erc20.mint(address(this), amount); + evmInvestmentManager.transfer(address(erc20), bytes32(bytes20(recipient)), amount); + assertEq(erc20.balanceOf(address(evmInvestmentManager.escrow())), amount); + + // Now we test the incoming message + homePools.incomingTransfer(currency, sender, bytes32(bytes20(recipient)), amount); + assertEq(erc20.balanceOf(address(evmInvestmentManager.escrow())), 0); + assertEq(erc20.balanceOf(recipient), amount); + } + + // Verify that funds are moved from the msg.sender into the escrow account + function testOutgoingTransferWorks( + string memory tokenName, + string memory tokenSymbol, + uint8 decimals, + uint128 initialBalance, + uint128 currency, + bytes32 recipient, + uint128 amount + ) public { + vm.assume(decimals > 0); + vm.assume(amount > 0); + vm.assume(currency != 0); + vm.assume(initialBalance >= amount); + + ERC20 erc20 = newErc20(tokenName, tokenSymbol, decimals); + + vm.expectRevert(bytes("InvestmentManager/unknown-currency")); + evmInvestmentManager.transfer(address(erc20), recipient, amount); + homePools.addCurrency(currency, address(erc20)); + + erc20.mint(address(this), initialBalance); + assertEq(erc20.balanceOf(address(this)), initialBalance); + assertEq(erc20.balanceOf(address(evmInvestmentManager.escrow())), 0); + erc20.approve(address(evmInvestmentManager), type(uint256).max); + + evmInvestmentManager.transfer(address(erc20), recipient, amount); + assertEq(erc20.balanceOf(address(this)), initialBalance - amount); + assertEq(erc20.balanceOf(address(evmInvestmentManager.escrow())), amount); + } + + // function testTransferTrancheTokensToCentrifuge( + // uint64 validUntil, + // bytes32 centChainAddress, + // uint128 amount, + // uint64 poolId, + // uint8 decimals, + // string memory tokenName, + // string memory tokenSymbol, + // bytes16 trancheId, + // uint128 currency + // ) public { + // vm.assume(currency > 0); + // vm.assume(validUntil > block.timestamp + 7 days); + + // address lPool_ = deployLiquidityPool(poolId, decimals, tokenName, tokenSymbol, trancheId, currency); + // homePools.updateMember(poolId, trancheId, address(this), validUntil); + + // // fund this account with amount + // homePools.incomingTransferTrancheTokens(poolId, trancheId, uint64(block.chainid), currency, address(this), amount); + + // // Verify the address(this) has the expected amount + // assertEq(LiquidityPool(lPool_).balanceOf(address(this)), amount); + + // // Now send the transfer from EVM -> Cent Chain + // LiquidityPool(lPool_).approve(address(evmInvestmentManager), amount); + // evmInvestmentManager.transferTrancheTokensToCentrifuge(poolId, trancheId, LiquidityPool(lPool_).asset(), centChainAddress, amount); + // assertEq(LiquidityPool(lPool_).balanceOf(address(this)), 0); + + // // Finally, verify the connector called `router.send` + // bytes memory message = Messages.formatTransferTrancheTokens( + // poolId, + // trancheId, + // bytes32(bytes20(address(this))), + // Messages.formatDomain(Messages.Domain.Centrifuge), + // currency, + // centChainAddress, + // amount + // ); + // assertEq(mockXcmRouter.sentMessages(message), true); + // } + + // function testTransferTrancheTokensFromCentrifuge( + // uint64 poolId, + // uint8 decimals, + // string memory tokenName, + // string memory tokenSymbol, + // bytes16 trancheId, + // uint128 currency, + // uint64 validUntil, + + // address destinationAddress, + // uint128 amount + // ) public { + // vm.assume(validUntil >= block.timestamp); + // vm.assume(destinationAddress != address(0)); + // vm.assume(currency > 0); + + // address lPool_ = deployLiquidityPool(poolId, decimals, tokenName, tokenSymbol, trancheId, currency); + + // homePools.updateMember(poolId, trancheId, destinationAddress, validUntil); + // assertTrue(LiquidityPool(lPool_).hasMember(destinationAddress)); + // homePools.incomingTransferTrancheTokens(poolId, trancheId, uint64(block.chainid), currency, destinationAddress, amount); + // assertEq(LiquidityPoolLike(lPool_).balanceOf(destinationAddress), amount); + // } + + // function testTransferTrancheTokensFromCentrifugeWithoutMemberFails( + // uint64 poolId, + // uint8 decimals, + // string memory tokenName, + // string memory tokenSymbol, + // bytes16 trancheId, + // uint128 currency, + // address destinationAddress, + // uint128 amount + // ) public { + // vm.assume(destinationAddress != address(0)); + // vm.assume(currency > 0); + + // deployLiquidityPool(poolId, decimals, tokenName, tokenSymbol, trancheId, currency); + + // vm.expectRevert(bytes("InvestmentManager/not-a-member")); + // homePools.incomingTransferTrancheTokens(poolId, trancheId, uint64(block.chainid), currency, destinationAddress, amount); + // } + + // function testTransferTrancheTokensToEVM( + // uint64 poolId, + // bytes16 trancheId, + // string memory tokenName, + // string memory tokenSymbol, + // uint8 decimals, + // uint64 validUntil, + // address destinationAddress, + // uint128 amount, + // uint128 currency + // ) public { + // vm.assume(validUntil > block.timestamp + 7 days); + // vm.assume(destinationAddress != address(0)); + // vm.assume(currency > 0); + // vm.assume(amount > 0); + + // address lPool_ = deployLiquidityPool(poolId, decimals, tokenName, tokenSymbol, trancheId, currency); + // homePools.updateMember(poolId, trancheId, destinationAddress, validUntil); + // homePools.updateMember(poolId, trancheId, address(this), validUntil); + // assertTrue(LiquidityPool(lPool_).hasMember(address(this))); + // assertTrue(LiquidityPool(lPool_).hasMember(destinationAddress)); + + // // Fund this address with amount + // homePools.incomingTransferTrancheTokens(poolId, trancheId, uint64(block.chainid), currency, address(this), amount); + // assertEq(LiquidityPool(lPool_).balanceOf(address(this)), amount); + + // // Approve and transfer amount from this address to destinationAddress + // LiquidityPool(lPool_).approve(address(evmInvestmentManager), amount); + // console.logAddress(lPool_); + // console.logAddress(LiquidityPool(lPool_).asset()); + // evmInvestmentManager.transferTrancheTokensToEVM( + // poolId, trancheId, LiquidityPool(lPool_).asset(), uint64(block.chainid), destinationAddress, amount + // ); + // assertEq(LiquidityPool(lPool_).balanceOf(address(this)), 0); + // } + + + // helpers + function deployLiquidityPool( + uint64 poolId, + uint8 decimals, + string memory tokenName, + string memory tokenSymbol, + bytes16 trancheId, + uint128 currency) internal returns (address lPool) { + // deploy liquidityPool + ERC20 erc20 = newErc20("X's Dollar", "USDX", 42); + homePools.addPool(poolId); // add pool + homePools.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, 0); // add tranche + homePools.addCurrency(currency, address(erc20)); + homePools.allowPoolCurrency(poolId, currency); + + lPool = evmInvestmentManager.deployLiquidityPool(poolId, trancheId, address(erc20)); + } + + function newErc20(string memory name, string memory symbol, uint8 decimals) internal returns (ERC20) { + ERC20 erc20 = new ERC20(decimals); + erc20.file("name", name); + erc20.file("symbol", symbol); + + return erc20; + } + + function stringToBytes32(string memory source) internal pure returns (bytes32 result) { + bytes memory tempEmptyStringTest = bytes(source); + if (tempEmptyStringTest.length == 0) { + return 0x0; + } + + assembly { + result := mload(add(source, 32)) + } + } + + function bytes32ToString(bytes32 _bytes32) internal pure returns (string memory) { + uint8 i = 0; + while (i < 32 && _bytes32[i] != 0) { + i++; + } + + bytes memory bytesArray = new bytes(i); + for (i = 0; i < 32 && _bytes32[i] != 0; i++) { + bytesArray[i] = _bytes32[i]; + } + return string(bytesArray); + } + + function toBytes32(bytes memory f) internal pure returns (bytes16 fc) { + assembly { + fc := mload(add(f, 32)) + } + return fc; + } + + function toBytes29(bytes memory f) internal pure returns (bytes29 fc) { + assembly { + fc := mload(add(f, 29)) + } + return fc; + } + + function hasDuplicates(bytes16[] calldata array) internal pure returns (bool) { + uint256 length = array.length; + for (uint256 i = 0; i < length; i++) { + for (uint256 j = i + 1; j < length; j++) { + if (array[i] == array[j]) { + return true; + } + } + } + return false; + } +} \ No newline at end of file diff --git a/test/LiquidityPool.t.sol b/test/LiquidityPool.t.sol new file mode 100644 index 00000000..2c6d9a1a --- /dev/null +++ b/test/LiquidityPool.t.sol @@ -0,0 +1,340 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.18; +pragma abicoder v2; + +import { InvestmentManager, Tranche } from "../src/InvestmentManager.sol"; +import { Gateway } from "../src/Gateway.sol"; +import { Escrow } from "../src/Escrow.sol"; +import { LiquidityPoolFactory, MemberlistFactory } from "../src/liquidityPool/Factory.sol"; +import { LiquidityPool } from "../src/liquidityPool/LiquidityPool.sol"; +import { ERC20 } from "../src/token/erc20.sol"; + +import { MemberlistLike, Memberlist } from "../src/token/memberlist.sol"; +import { MockHomeLiquidityPools } from "./mock/MockHomeLiquidityPools.sol"; +import { MockXcmRouter } from "./mock/MockXcmRouter.sol"; +import { Messages } from "../src/Messages.sol"; +import { PauseAdmin } from "../src/admin/PauseAdmin.sol"; +import { DelayedAdmin } from "../src/admin/DelayedAdmin.sol"; +import "forge-std/Test.sol"; +import "../src/InvestmentManager.sol"; + +interface EscrowLike_ { + function approve(address token, address spender, uint256 value) external; + function rely(address usr) external; +} + +interface AuthLike { + function wards(address user) external returns (uint); +} + +contract LiquidityPoolTest is Test { + + uint128 constant MAX_UINT128 = type(uint128).max; + + InvestmentManager evmInvestmentManager; + Gateway gateway; + MockHomeLiquidityPools homePools; + MockXcmRouter mockXcmRouter; + Escrow escrow; + ERC20 erc20; + + + function setUp() public { + vm.chainId(1); + uint256 shortWait = 24 hours; + uint256 longWait = 48 hours; + uint256 gracePeriod = 48 hours; + escrow = new Escrow(); + erc20 = newErc20("X's Dollar", "USDX", 42); + address liquidityPoolFactory_ = address(new LiquidityPoolFactory()); + address memberlistFactory_ = address(new MemberlistFactory()); + + evmInvestmentManager = new InvestmentManager(address(escrow), liquidityPoolFactory_, memberlistFactory_); + + mockXcmRouter = new MockXcmRouter(address(evmInvestmentManager)); + + homePools = new MockHomeLiquidityPools(address(mockXcmRouter)); + PauseAdmin pauseAdmin = new PauseAdmin(); + DelayedAdmin delayedAdmin = new DelayedAdmin(); + + gateway = + new Gateway(address(evmInvestmentManager), address(mockXcmRouter), shortWait, longWait, gracePeriod); + gateway.rely(address(pauseAdmin)); + gateway.rely(address(delayedAdmin)); + pauseAdmin.file("gateway", address(gateway)); + delayedAdmin.file("gateway", address(gateway)); + evmInvestmentManager.file("gateway", address(gateway)); + escrow.rely(address(evmInvestmentManager)); + mockXcmRouter.file("gateway", address(gateway)); + evmInvestmentManager.rely(address(gateway)); + escrow.rely(address(gateway)); + } + + function testDepositMint( + uint64 poolId, + uint8 decimals, + string memory tokenName, + string memory tokenSymbol, + bytes16 trancheId, + uint128 price, + uint128 currencyId, + uint256 amount, + uint64 validUntil + ) public { + vm.assume(currencyId > 0); + vm.assume(amount < MAX_UINT128); + vm.assume(amount > 1); + vm.assume(validUntil >= block.timestamp); + price = 2; + + address lPool_ = deployLiquidityPool(poolId, decimals, tokenName, tokenSymbol, trancheId, price, currencyId, address(erc20)); + LiquidityPool lPool = LiquidityPool(lPool_); + + erc20.mint(address(this), amount); + + // will fail - user not member: can not receive trancheToken + vm.expectRevert(bytes("InvestmentManager/not-a-member")); + lPool.requestDeposit(amount); + homePools.updateMember(poolId, trancheId, address(this), validUntil); // add user as member + + // will fail - user did not give currency allowance to investmentManager + vm.expectRevert(bytes("ERC20/insufficient-allowance")); + lPool.requestDeposit(amount); + erc20.approve(address(evmInvestmentManager), amount); // add allowance + + lPool.requestDeposit(amount); + + // ensure funds are locked in escrow + assertEq(erc20.balanceOf(address(escrow)), amount); + assertEq(erc20.balanceOf(address(this)), 0); + + // trigger executed collectInvest + uint128 _currencyId = evmInvestmentManager.currencyAddressToId(address(erc20)); // retrieve currencyId + uint128 trancheTokensPayout = uint128(amount) / price; // trancheTokenPrice = 2$ + homePools.isExecutedCollectInvest(poolId, trancheId, bytes32(bytes20(address(this))), _currencyId, uint128(amount), trancheTokensPayout); + + // assert deposit & mint values adjusted + assertEq(lPool.maxMint(address(this)), trancheTokensPayout); // max deposit + assertEq(lPool.maxDeposit(address(this)), amount); // max deposit + // assert tranche tokens minted + assertEq(lPool.balanceOf(address(escrow)), trancheTokensPayout); + + // deposit a share of the amount + uint share = 2; + lPool.deposit(amount/share, address(this)); // mint hald the amount + assertEq(lPool.balanceOf(address(this)), trancheTokensPayout/share); + assertEq(lPool.balanceOf(address(escrow)), trancheTokensPayout - trancheTokensPayout/share); + assertEq(lPool.maxMint(address(this)), trancheTokensPayout - trancheTokensPayout/share); // max deposit + assertEq(lPool.maxDeposit(address(this)), amount - amount/share); // max deposit + + // mint the rest + lPool.mint(lPool.maxMint(address(this)), address(this)); + assertEq(lPool.balanceOf(address(this)), trancheTokensPayout - lPool.maxMint(address(this))); + assertTrue(lPool.balanceOf(address(escrow)) <= 1); + assertTrue(lPool.maxMint(address(this)) <= 1 ); + // assertTrue(lPool.maxDeposit(address(this)) <= 2); // todo: fix rounding + } + + function testRedeem( + uint64 poolId, + uint8 decimals, + string memory tokenName, + string memory tokenSymbol, + bytes16 trancheId, + uint128 price, + uint128 currencyId, + uint256 amount, + uint64 validUntil + ) public { + vm.assume(currencyId > 0); + vm.assume(amount < MAX_UINT128); + vm.assume(amount > 1); + vm.assume(validUntil >= block.timestamp); + price = 1; + + address lPool_ = deployLiquidityPool(poolId, decimals, tokenName, tokenSymbol, trancheId, price, currencyId, address(erc20)); + deposit(lPool_, poolId, trancheId, amount, validUntil); // deposit funds first + LiquidityPool lPool = LiquidityPool(lPool_); + + // will fail - user did not give tranche token allowance to investmentManager + vm.expectRevert(bytes("InvestmentManager/insufficient-balance")); + lPool.requestDeposit(amount); + lPool.approve(address(evmInvestmentManager), amount); // add allowance + + lPool.requestRedeem(amount); + assertEq(lPool.balanceOf(address(escrow)), amount); + + // trigger executed collectRedeem + uint128 _currencyId = evmInvestmentManager.currencyAddressToId(address(erc20)); // retrieve currencyId + uint128 currencyPayout = uint128(amount) / price; + homePools.isExecutedCollectRedeem(poolId, trancheId, bytes32(bytes20(address(this))), _currencyId, currencyPayout, uint128(amount)); + + // assert withdraw & redeem values adjusted + assertEq(lPool.maxWithdraw(address(this)), currencyPayout); // max deposit + assertEq(lPool.maxRedeem(address(this)), amount); // max deposit + assertEq(lPool.balanceOf(address(escrow)), 0); + + console.logUint(lPool.maxRedeem(address(this))); + console.logUint(amount); + + lPool.redeem(amount, address(this), address(this)); // mint hald the amount + assertEq(lPool.balanceOf(address(this)), 0); + assertEq(lPool.balanceOf(address(escrow)), 0); + assertEq(erc20.balanceOf(address(this)), amount); + assertEq(lPool.maxMint(address(this)), 0); + assertEq(lPool.maxDeposit(address(this)),0); + } + + // helpers + + function testWithdraw( + uint64 poolId, + uint8 decimals, + string memory tokenName, + string memory tokenSymbol, + bytes16 trancheId, + uint128 price, + uint128 currencyId, + uint256 amount, + uint64 validUntil + ) public { + vm.assume(currencyId > 0); + vm.assume(amount < MAX_UINT128); + vm.assume(amount > 1); + vm.assume(validUntil >= block.timestamp); + price = 1; + + address lPool_ = deployLiquidityPool(poolId, decimals, tokenName, tokenSymbol, trancheId, price, currencyId, address(erc20)); + deposit(lPool_, poolId, trancheId, amount, validUntil); // deposit funds first + LiquidityPool lPool = LiquidityPool(lPool_); + + // will fail - user did not give tranche token allowance to investmentManager + vm.expectRevert(bytes("InvestmentManager/insufficient-balance")); + lPool.requestDeposit(amount); + lPool.approve(address(evmInvestmentManager), amount); // add allowance + + lPool.requestRedeem(amount); + assertEq(lPool.balanceOf(address(escrow)), amount); + + // trigger executed collectRedeem + uint128 _currencyId = evmInvestmentManager.currencyAddressToId(address(erc20)); // retrieve currencyId + uint128 currencyPayout = uint128(amount) / price; + homePools.isExecutedCollectRedeem(poolId, trancheId, bytes32(bytes20(address(this))), _currencyId, currencyPayout, uint128(amount)); + + // assert withdraw & redeem values adjusted + assertEq(lPool.maxWithdraw(address(this)), currencyPayout); // max deposit + assertEq(lPool.maxRedeem(address(this)), amount); // max deposit + assertEq(lPool.balanceOf(address(escrow)), 0); + + console.logUint(lPool.maxRedeem(address(this))); + console.logUint(amount); + + lPool.withdraw(amount, address(this), address(this)); // mint hald the amount + assertEq(lPool.balanceOf(address(this)), 0); + assertEq(lPool.balanceOf(address(escrow)), 0); + assertEq(erc20.balanceOf(address(this)), amount); + assertEq(lPool.maxMint(address(this)), 0); + assertEq(lPool.maxDeposit(address(this)),0); + } + + function testCollectInvest( + uint64 poolId, + bytes16 trancheId, + string memory tokenName, + string memory tokenSymbol, + uint8 decimals, + uint128 price, + uint128 currency, + uint64 validUntil, + uint128 amount + ) public { + vm.assume(amount > 0); + vm.assume(currency > 0); + vm.assume(decimals > 0); + vm.assume(validUntil > block.timestamp + 7 days); + + address lPool_ = deployLiquidityPool(poolId, decimals, tokenName, tokenSymbol, trancheId, price, currency, address(erc20)); + LiquidityPool lPool = LiquidityPool(lPool_); + + vm.expectRevert(bytes("InvestmentManager/not-a-member")); + lPool.collectInvest(address(this)); + + homePools.updateMember(poolId, trancheId, address(this), validUntil); + lPool.collectInvest(address(this)); + } + + + function testCollectRedeem( + uint64 poolId, + bytes16 trancheId, + string memory tokenName, + string memory tokenSymbol, + uint8 decimals, + uint128 price, + uint128 currency, + uint8 trancheDecimals, + uint64 validUntil, + uint128 amount + ) public { + vm.assume(amount > 0); + vm.assume(currency > 0); + vm.assume(trancheDecimals > 0); + vm.assume(validUntil > block.timestamp + 7 days); + + address lPool_ = deployLiquidityPool(poolId, decimals, tokenName, tokenSymbol, trancheId, price, currency, address(erc20)); + LiquidityPool lPool = LiquidityPool(lPool_); + homePools.allowPoolCurrency(poolId, currency); + + vm.expectRevert(bytes("InvestmentManager/not-a-member")); + lPool.collectRedeem(address(this)); + homePools.updateMember(poolId, trancheId, address(this), validUntil); + + lPool.collectRedeem(address(this)); + } + + function deposit( + address _lPool, + uint64 poolId, + bytes16 trancheId, + uint256 amount, + uint64 validUntil) + public { + LiquidityPool lPool = LiquidityPool(_lPool); + erc20.mint(address(this), amount); + homePools.updateMember(poolId, trancheId, address(this), validUntil); // add user as member + erc20.approve(address(evmInvestmentManager), amount); // add allowance + lPool.requestDeposit(amount); + // trigger executed collectInvest + uint128 currencyId = evmInvestmentManager.currencyAddressToId(address(erc20)); // retrieve currencyId + homePools.isExecutedCollectInvest(poolId, trancheId, bytes32(bytes20(address(this))), currencyId, uint128(amount), uint128(amount)); + lPool.deposit(amount, address(this)); // withdraw hald the amount + } + + function deployLiquidityPool( + uint64 poolId, + uint8 decimals, + string memory tokenName, + string memory tokenSymbol, + bytes16 trancheId, + uint128 price, + uint128 currency, + address erc20 + ) public returns (address) { + homePools.addPool(poolId); // add pool + homePools.addTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); // add tranche + homePools.addCurrency(currency, address(erc20)); + homePools.allowPoolCurrency(poolId, currency); + + address lPoolAddress = evmInvestmentManager.deployLiquidityPool(poolId, trancheId, address(erc20)); + return lPoolAddress; + } + + function newErc20(string memory name, string memory symbol, uint8 decimals) internal returns (ERC20) { + ERC20 erc20 = new ERC20(decimals); + erc20.file("name", name); + erc20.file("symbol", symbol); + return erc20; + } + + +} \ No newline at end of file diff --git a/test/Messages.t.sol b/test/Messages.t.sol index 0f354dfe..d6efc128 100644 --- a/test/Messages.t.sol +++ b/test/Messages.t.sol @@ -3,13 +3,13 @@ pragma solidity ^0.8.18; pragma abicoder v2; import {TypedMemView} from "memview-sol/TypedMemView.sol"; -import {ConnectorMessages} from "src/Messages.sol"; +import {Messages} from "src/Messages.sol"; import "forge-std/Test.sol"; contract MessagesTest is Test { using TypedMemView for bytes; using TypedMemView for bytes29; - using ConnectorMessages for bytes29; + using Messages for bytes29; function setUp() public {} @@ -18,17 +18,17 @@ contract MessagesTest is Test { address currencyAddress = 0x1231231231231231231231231231231231231231; bytes memory expectedHex = hex"010000000000000000000000000eb5ec7b1231231231231231231231231231231231231231"; - assertEq(ConnectorMessages.formatAddCurrency(currency, currencyAddress), expectedHex); + assertEq(Messages.formatAddCurrency(currency, currencyAddress), expectedHex); (uint128 decodedCurrency, address decodedCurrencyAddress) = - ConnectorMessages.parseAddCurrency(expectedHex.ref(0)); + Messages.parseAddCurrency(expectedHex.ref(0)); assertEq(uint256(decodedCurrency), currency); assertEq(decodedCurrencyAddress, currencyAddress); } function testAddCurrencyEquivalence(uint128 currency, address currencyAddress) public { - bytes memory _message = ConnectorMessages.formatAddCurrency(currency, currencyAddress); - (uint128 decodedCurrency, address decodedCurrencyAddress) = ConnectorMessages.parseAddCurrency(_message.ref(0)); + bytes memory _message = Messages.formatAddCurrency(currency, currencyAddress); + (uint128 decodedCurrency, address decodedCurrencyAddress) = Messages.parseAddCurrency(_message.ref(0)); assertEq(decodedCurrency, uint256(currency)); assertEq(decodedCurrencyAddress, currencyAddress); } @@ -37,15 +37,15 @@ contract MessagesTest is Test { uint64 poolId = 12378532; bytes memory expectedHex = hex"020000000000bce1a4"; - assertEq(ConnectorMessages.formatAddPool(poolId), expectedHex); + assertEq(Messages.formatAddPool(poolId), expectedHex); - (uint64 decodedPoolId) = ConnectorMessages.parseAddPool(expectedHex.ref(0)); + (uint64 decodedPoolId) = Messages.parseAddPool(expectedHex.ref(0)); assertEq(uint256(decodedPoolId), poolId); } function testAddPoolEquivalence(uint64 poolId) public { - bytes memory _message = ConnectorMessages.formatAddPool(poolId); - (uint64 decodedPoolId) = ConnectorMessages.parseAddPool(_message.ref(0)); + bytes memory _message = Messages.formatAddPool(poolId); + (uint64 decodedPoolId) = Messages.parseAddPool(_message.ref(0)); assertEq(decodedPoolId, uint256(poolId)); } @@ -54,16 +54,16 @@ contract MessagesTest is Test { uint128 currency = 246803579; bytes memory expectedHex = hex"030000000000bce1a40000000000000000000000000eb5ec7b"; - assertEq(ConnectorMessages.formatAllowPoolCurrency(poolId, currency), expectedHex); + assertEq(Messages.formatAllowPoolCurrency(poolId, currency), expectedHex); - (uint64 decodedPoolId, uint128 decodedCurrency) = ConnectorMessages.parseAllowPoolCurrency(expectedHex.ref(0)); + (uint64 decodedPoolId, uint128 decodedCurrency) = Messages.parseAllowPoolCurrency(expectedHex.ref(0)); assertEq(decodedPoolId, poolId); assertEq(uint256(decodedCurrency), currency); } function testAllowPoolCurrencyEquivalence(uint128 currency, uint64 poolId) public { - bytes memory _message = ConnectorMessages.formatAllowPoolCurrency(poolId, currency); - (uint64 decodedPoolId, uint128 decodedCurrency) = ConnectorMessages.parseAllowPoolCurrency(_message.ref(0)); + bytes memory _message = Messages.formatAllowPoolCurrency(poolId, currency); + (uint64 decodedPoolId, uint128 decodedCurrency) = Messages.parseAllowPoolCurrency(_message.ref(0)); assertEq(uint256(decodedPoolId), uint256(poolId)); assertEq(decodedCurrency, uint256(currency)); } @@ -78,7 +78,7 @@ contract MessagesTest is Test { bytes memory expectedHex = hex"040000000000000001811acd5b3f17c06841c7e41e9e04cb1b536f6d65204e616d65000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000053594d424f4c00000000000000000000000000000000000000000000000000000f00000000033b2e3c9fd0803ce8000000"; - assertEq(ConnectorMessages.formatAddTranche(poolId, trancheId, name, symbol, decimals, price), expectedHex); + assertEq(Messages.formatAddTranche(poolId, trancheId, name, symbol, decimals, price), expectedHex); ( uint64 decodedPoolId, @@ -87,7 +87,7 @@ contract MessagesTest is Test { string memory decodedTokenSymbol, uint8 decodedDecimals, uint128 decodedPrice - ) = ConnectorMessages.parseAddTranche(expectedHex.ref(0)); + ) = Messages.parseAddTranche(expectedHex.ref(0)); assertEq(uint256(decodedPoolId), poolId); assertEq(decodedTrancheId, trancheId); @@ -106,7 +106,7 @@ contract MessagesTest is Test { uint128 price ) public { bytes memory _message = - ConnectorMessages.formatAddTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); + Messages.formatAddTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); ( uint64 decodedPoolId, bytes16 decodedTrancheId, @@ -114,7 +114,7 @@ contract MessagesTest is Test { string memory decodedTokenSymbol, uint8 decodedDecimals, uint128 decodedPrice - ) = ConnectorMessages.parseAddTranche(_message.ref(0)); + ) = Messages.parseAddTranche(_message.ref(0)); assertEq(uint256(decodedPoolId), uint256(poolId)); assertEq(decodedTrancheId, trancheId); // Comparing raw input to output can erroneously fail when a byte string is given. @@ -134,19 +134,19 @@ contract MessagesTest is Test { bytes memory expectedHex = hex"050000000000000001811acd5b3f17c06841c7e41e9e04cb1b00000000033b2e3c9fd0803ce8000000"; - assertEq(ConnectorMessages.formatUpdateTrancheTokenPrice(poolId, trancheId, price), expectedHex); + assertEq(Messages.formatUpdateTrancheTokenPrice(poolId, trancheId, price), expectedHex); (uint64 decodedPoolId, bytes16 decodedTrancheId, uint128 decodedPrice) = - ConnectorMessages.parseUpdateTrancheTokenPrice(expectedHex.ref(0)); + Messages.parseUpdateTrancheTokenPrice(expectedHex.ref(0)); assertEq(uint256(decodedPoolId), poolId); assertEq(decodedTrancheId, trancheId); assertEq(decodedPrice, price); } function testUpdateTrancheTokenPriceEquivalence(uint64 poolId, bytes16 trancheId, uint128 price) public { - bytes memory _message = ConnectorMessages.formatUpdateTrancheTokenPrice(poolId, trancheId, price); + bytes memory _message = Messages.formatUpdateTrancheTokenPrice(poolId, trancheId, price); (uint64 decodedPoolId, bytes16 decodedTrancheId, uint128 decodedPrice) = - ConnectorMessages.parseUpdateTrancheTokenPrice(_message.ref(0)); + Messages.parseUpdateTrancheTokenPrice(_message.ref(0)); assertEq(uint256(decodedPoolId), uint256(poolId)); assertEq(decodedTrancheId, trancheId); assertEq(uint256(decodedPrice), uint256(price)); @@ -163,10 +163,10 @@ contract MessagesTest is Test { bytes memory expectedHex = hex"060000000000000002811acd5b3f17c06841c7e41e9e04cb1b45645645645645645645645645645645645645645645645645645645645645640000000065b376aa"; - assertEq(ConnectorMessages.formatUpdateMember(poolId, trancheId, member, validUntil), expectedHex); + assertEq(Messages.formatUpdateMember(poolId, trancheId, member, validUntil), expectedHex); (uint64 decodedPoolId, bytes16 decodedTrancheId, address decodedMember, uint64 decodedValidUntil) = - ConnectorMessages.parseUpdateMember(expectedHex.ref(0)); + Messages.parseUpdateMember(expectedHex.ref(0)); assertEq(uint256(decodedPoolId), poolId); assertEq(decodedTrancheId, trancheId); assertEq(decodedMember, address(bytes20(member))); @@ -174,9 +174,9 @@ contract MessagesTest is Test { } function testUpdateMemberEquivalence(uint64 poolId, bytes16 trancheId, address user, uint64 validUntil) public { - bytes memory _message = ConnectorMessages.formatUpdateMember(poolId, trancheId, user, validUntil); + bytes memory _message = Messages.formatUpdateMember(poolId, trancheId, user, validUntil); (uint64 decodedPoolId, bytes16 decodedTrancheId, address decodedUser, uint64 decodedValidUntil) = - ConnectorMessages.parseUpdateMember(_message.ref(0)); + Messages.parseUpdateMember(_message.ref(0)); assertEq(uint256(decodedPoolId), uint256(poolId)); assertEq(decodedTrancheId, trancheId); assertEq(decodedUser, user); @@ -191,10 +191,10 @@ contract MessagesTest is Test { bytes memory expectedHex = hex"070000000000000000000000000eb5ec7b45645645645645645645645645645645645645645645645645645645645645641231231231231231231231231231231231231231000000000000000000000000000000000052b7d2dcc80cd2e4000000"; - assertEq(ConnectorMessages.formatTransfer(currency, sender, bytes32(bytes20(receiver)), amount), expectedHex); + assertEq(Messages.formatTransfer(currency, sender, bytes32(bytes20(receiver)), amount), expectedHex); (uint128 decodedCurrency, bytes32 decodedSender, bytes32 decodedReceiver, uint128 decodedAmount) = - ConnectorMessages.parseTransfer(expectedHex.ref(0)); + Messages.parseTransfer(expectedHex.ref(0)); assertEq(uint256(decodedCurrency), currency); assertEq(decodedSender, sender); assertEq(decodedReceiver, bytes32(bytes20(receiver))); @@ -202,16 +202,16 @@ contract MessagesTest is Test { // Test the optimised `parseIncomingTransfer` now (uint128 decodedCurrency2, address decodedReceiver2, uint128 decodedAmount2) = - ConnectorMessages.parseIncomingTransfer(expectedHex.ref(0)); + Messages.parseIncomingTransfer(expectedHex.ref(0)); assertEq(uint256(decodedCurrency2), currency); assertEq(decodedReceiver2, receiver); assertEq(decodedAmount, amount); } function testTransferEquivalence(uint128 token, bytes32 sender, bytes32 receiver, uint128 amount) public { - bytes memory _message = ConnectorMessages.formatTransfer(token, sender, receiver, amount); + bytes memory _message = Messages.formatTransfer(token, sender, receiver, amount); (uint128 decodedToken, bytes32 decodedSender, bytes32 decodedReceiver, uint128 decodedAmount) = - ConnectorMessages.parseTransfer(_message.ref(0)); + Messages.parseTransfer(_message.ref(0)); assertEq(uint256(decodedToken), uint256(token)); assertEq(decodedSender, sender); assertEq(decodedReceiver, receiver); @@ -219,7 +219,7 @@ contract MessagesTest is Test { // Test the optimised `parseIncomingTransfer` now (uint128 decodedToken2, address decodedRecipient2, uint128 decodedAmount2) = - ConnectorMessages.parseIncomingTransfer(_message.ref(0)); + Messages.parseIncomingTransfer(_message.ref(0)); assertEq(uint256(decodedToken2), uint256(decodedToken)); assertEq(decodedRecipient2, address(bytes20(decodedReceiver))); assertEq(decodedAmount, decodedAmount2); @@ -229,20 +229,20 @@ contract MessagesTest is Test { uint64 poolId = 1; bytes16 trancheId = bytes16(hex"811acd5b3f17c06841c7e41e9e04cb1b"); bytes32 sender = bytes32(0x4564564564564564564564564564564564564564564564564564564564564564); - bytes9 domain = ConnectorMessages.formatDomain(ConnectorMessages.Domain.EVM, 1284); + bytes9 domain = Messages.formatDomain(Messages.Domain.EVM, 1284); address receiver = 0x1231231231231231231231231231231231231231; uint128 amount = 100000000000000000000000000; uint128 currency = 1; bytes memory expectedHex = - hex"080000000000000001811acd5b3f17c06841c7e41e9e04cb1b45645645645645645645645645645645645645645645645645645645645645640100000000000005041231231231231231231231231231231231231231000000000000000000000000000000000052b7d2dcc80cd2e4000000"; + hex"080000000000000001811acd5b3f17c06841c7e41e9e04cb1b4564564564564564564564564564564564564564564564564564564564564564010000000000000504000000000000000000000000000000011231231231231231231231231231231231231231000000000000000000000000000000000052b7d2dcc80cd2e4000000"; assertEq( - ConnectorMessages.formatTransferTrancheTokens(poolId, trancheId, sender, domain, currency, receiver, amount), + Messages.formatTransferTrancheTokens(poolId, trancheId, sender, domain, currency, receiver, amount), expectedHex ); (uint64 decodedPoolId, bytes16 decodedTrancheId, uint decodedCurrency, address decodedReceiver, uint128 decodedAmount) = - ConnectorMessages.parseTransferTrancheTokens20(expectedHex.ref(0)); + Messages.parseTransferTrancheTokens20(expectedHex.ref(0)); assertEq(uint256(decodedPoolId), poolId); assertEq(decodedTrancheId, trancheId); assertEq(decodedCurrency, currency); @@ -255,7 +255,7 @@ contract MessagesTest is Test { // uint64 poolId = 1; // bytes16 trancheId = bytes16(hex"811acd5b3f17c06841c7e41e9e04cb1b"); // address sender = 0x1231231231231231231231231231231231231231; - // bytes9 domain = ConnectorMessages.formatDomain(ConnectorMessages.Domain.Centrifuge); + // bytes9 domain = Messages.formatDomain(Messages.Domain.Centrifuge); // bytes32 receiver = bytes32(0x4564564564564564564564564564564564564564564564564564564564564564); // uint128 amount = 100000000000000000000000000; // uint128 currency = 1; @@ -263,7 +263,7 @@ contract MessagesTest is Test { // hex"080000000000000001811acd5b3f17c06841c7e41e9e04cb1b12312312312312312312312312312312312312310000000000000000000000000000000000000000004564564564564564564564564564564564564564564564564564564564564564000000000052b7d2dcc80cd2e4000000"; // assertEq( - // ConnectorMessages.formatTransferTrancheTokens( + // Messages.formatTransferTrancheTokens( // poolId, trancheId, bytes32(bytes20(sender)), domain, currency, receiver, amount // ), // expectedHex @@ -277,7 +277,7 @@ contract MessagesTest is Test { // uint128 decodedCurrency, // bytes32 decodedReceiver, // uint128 decodedAmount - // ) = ConnectorMessages.parseTransferTrancheTokens32(expectedHex.ref(0)); + // ) = Messages.parseTransferTrancheTokens32(expectedHex.ref(0)); // assertEq(uint256(decodedPoolId), poolId); // assertEq(decodedTrancheId, trancheId); // assertEq(decodedSender, bytes32(bytes20(sender))); @@ -296,54 +296,24 @@ contract MessagesTest is Test { address destinationAddress, uint128 amount ) public { - bytes memory _message = ConnectorMessages.formatTransferTrancheTokens( + bytes memory _message = Messages.formatTransferTrancheTokens( poolId, trancheId, sender, - ConnectorMessages.formatDomain(ConnectorMessages.Domain.EVM, destinationChainId), + Messages.formatDomain(Messages.Domain.EVM, destinationChainId), currency, destinationAddress, amount ); - (uint64 decodedPoolId, bytes16 decodedTrancheId, uint decodedCurrency, address decodedDestinationAddress, uint256 decodedAmount) = - ConnectorMessages.parseTransferTrancheTokens20(_message.ref(0)); + (uint64 decodedPoolId, bytes16 decodedTrancheId, uint128 decodedCurrency, address decodedDestinationAddress, uint256 decodedAmount) = + Messages.parseTransferTrancheTokens20(_message.ref(0)); assertEq(uint256(decodedPoolId), uint256(poolId)); assertEq(decodedTrancheId, trancheId); assertEq(decodedCurrency, currency); assertEq(decodedDestinationAddress, destinationAddress); assertEq(decodedAmount, amount); } - //TODO: fix stack issue - // function testTransferTrancheTokensToCentrifugeEquivalence( - // uint64 poolId, - // bytes16 trancheId, - // address sender, - // uint128 currency, - // bytes32 destinationAddress, - // uint128 amount - // ) public { - // bytes9 inputEncodedDomain = ConnectorMessages.formatDomain(ConnectorMessages.Domain.Centrifuge); - // bytes memory _message = ConnectorMessages.formatTransferTrancheTokens( - // poolId, trancheId, bytes32(bytes20(sender)), inputEncodedDomain, currency, destinationAddress, amount - // ); - // ( - // uint64 decodedPoolId, - // bytes16 decodedTrancheId, - // bytes32 decodedSender, - // bytes9 encodedDomain, - // uint128 decodedCurrency, - // bytes32 decodedDestinationAddress, - // uint256 decodedAmount - // ) = ConnectorMessages.parseTransferTrancheTokens32(_message.ref(0)); - // assertEq(uint256(decodedPoolId), uint256(poolId)); - // assertEq(decodedTrancheId, trancheId); - // assertEq(decodedSender, bytes32(bytes20(sender))); - // assertEq(encodedDomain, inputEncodedDomain); - // assertEq(decodedDestinationAddress, destinationAddress); - // assertEq(decodedCurrency, currency); - // assertEq(decodedAmount, amount); - // } function testIncreaseInvestOrder() public { uint64 poolId = 1; @@ -355,7 +325,7 @@ contract MessagesTest is Test { hex"090000000000000001811acd5b3f17c06841c7e41e9e04cb1b45645645645645645645645645645645645645645645645645645645645645640000000000000000000000000eb5ec7b000000000052b7d2dcc80cd2e4000000"; assertEq( - ConnectorMessages.formatIncreaseInvestOrder(poolId, trancheId, investor, currency, amount), expectedHex + Messages.formatIncreaseInvestOrder(poolId, trancheId, investor, currency, amount), expectedHex ); ( @@ -364,7 +334,7 @@ contract MessagesTest is Test { bytes32 decodedInvestor, uint128 decodedCurrency, uint128 decodedAmount - ) = ConnectorMessages.parseIncreaseInvestOrder(expectedHex.ref(0)); + ) = Messages.parseIncreaseInvestOrder(expectedHex.ref(0)); assertEq(uint256(decodedPoolId), poolId); assertEq(decodedTrancheId, trancheId); assertEq(decodedInvestor, investor); @@ -379,14 +349,14 @@ contract MessagesTest is Test { uint128 token, uint128 amount ) public { - bytes memory _message = ConnectorMessages.formatIncreaseInvestOrder(poolId, trancheId, investor, token, amount); + bytes memory _message = Messages.formatIncreaseInvestOrder(poolId, trancheId, investor, token, amount); ( uint64 decodedPoolId, bytes16 decodedTrancheId, bytes32 decodedInvestor, uint128 decodedToken, uint128 decodedAmount - ) = ConnectorMessages.parseIncreaseInvestOrder(_message.ref(0)); + ) = Messages.parseIncreaseInvestOrder(_message.ref(0)); assertEq(uint256(decodedPoolId), uint256(poolId)); assertEq(decodedTrancheId, trancheId); @@ -405,7 +375,7 @@ contract MessagesTest is Test { hex"0a0000000000000001811acd5b3f17c06841c7e41e9e04cb1b45645645645645645645645645645645645645645645645645645645645645640000000000000000000000000eb5ec7b000000000052b7d2dcc80cd2e4000000"; assertEq( - ConnectorMessages.formatDecreaseInvestOrder(poolId, trancheId, investor, currency, amount), expectedHex + Messages.formatDecreaseInvestOrder(poolId, trancheId, investor, currency, amount), expectedHex ); ( @@ -414,7 +384,7 @@ contract MessagesTest is Test { bytes32 decodedInvestor, uint128 decodedCurrency, uint128 decodedAmount - ) = ConnectorMessages.parseDecreaseInvestOrder(expectedHex.ref(0)); + ) = Messages.parseDecreaseInvestOrder(expectedHex.ref(0)); assertEq(uint256(decodedPoolId), poolId); assertEq(decodedTrancheId, trancheId); assertEq(decodedInvestor, investor); @@ -429,14 +399,14 @@ contract MessagesTest is Test { uint128 token, uint128 amount ) public { - bytes memory _message = ConnectorMessages.formatDecreaseInvestOrder(poolId, trancheId, investor, token, amount); + bytes memory _message = Messages.formatDecreaseInvestOrder(poolId, trancheId, investor, token, amount); ( uint64 decodedPoolId, bytes16 decodedTrancheId, bytes32 decodedInvestor, uint128 decodedToken, uint128 decodedAmount - ) = ConnectorMessages.parseDecreaseInvestOrder(_message.ref(0)); + ) = Messages.parseDecreaseInvestOrder(_message.ref(0)); assertEq(uint256(decodedPoolId), uint256(poolId)); assertEq(decodedTrancheId, trancheId); @@ -455,7 +425,7 @@ contract MessagesTest is Test { hex"0b0000000000000001811acd5b3f17c06841c7e41e9e04cb1b45645645645645645645645645645645645645645645645645645645645645640000000000000000000000000eb5ec7b000000000052b7d2dcc80cd2e4000000"; assertEq( - ConnectorMessages.formatIncreaseRedeemOrder(poolId, trancheId, investor, currency, amount), expectedHex + Messages.formatIncreaseRedeemOrder(poolId, trancheId, investor, currency, amount), expectedHex ); ( @@ -464,7 +434,7 @@ contract MessagesTest is Test { bytes32 decodedInvestor, uint128 decodedCurrency, uint128 decodedAmount - ) = ConnectorMessages.parseIncreaseRedeemOrder(expectedHex.ref(0)); + ) = Messages.parseIncreaseRedeemOrder(expectedHex.ref(0)); assertEq(uint256(decodedPoolId), poolId); assertEq(decodedTrancheId, trancheId); assertEq(decodedInvestor, investor); @@ -479,14 +449,14 @@ contract MessagesTest is Test { uint128 token, uint128 amount ) public { - bytes memory _message = ConnectorMessages.formatIncreaseRedeemOrder(poolId, trancheId, investor, token, amount); + bytes memory _message = Messages.formatIncreaseRedeemOrder(poolId, trancheId, investor, token, amount); ( uint64 decodedPoolId, bytes16 decodedTrancheId, bytes32 decodedInvestor, uint128 decodedToken, uint128 decodedAmount - ) = ConnectorMessages.parseIncreaseRedeemOrder(_message.ref(0)); + ) = Messages.parseIncreaseRedeemOrder(_message.ref(0)); assertEq(uint256(decodedPoolId), uint256(poolId)); assertEq(decodedTrancheId, trancheId); @@ -505,7 +475,7 @@ contract MessagesTest is Test { hex"0c0000000000000001811acd5b3f17c06841c7e41e9e04cb1b45645645645645645645645645645645645645645645645645645645645645640000000000000000000000000eb5ec7b000000000052b7d2dcc80cd2e4000000"; assertEq( - ConnectorMessages.formatDecreaseRedeemOrder(poolId, trancheId, investor, currency, amount), expectedHex + Messages.formatDecreaseRedeemOrder(poolId, trancheId, investor, currency, amount), expectedHex ); ( @@ -514,7 +484,7 @@ contract MessagesTest is Test { bytes32 decodedInvestor, uint128 decodedCurrency, uint128 decodedAmount - ) = ConnectorMessages.parseDecreaseRedeemOrder(expectedHex.ref(0)); + ) = Messages.parseDecreaseRedeemOrder(expectedHex.ref(0)); assertEq(uint256(decodedPoolId), poolId); assertEq(decodedTrancheId, trancheId); assertEq(decodedInvestor, investor); @@ -529,14 +499,14 @@ contract MessagesTest is Test { uint128 token, uint128 amount ) public { - bytes memory _message = ConnectorMessages.formatDecreaseRedeemOrder(poolId, trancheId, investor, token, amount); + bytes memory _message = Messages.formatDecreaseRedeemOrder(poolId, trancheId, investor, token, amount); ( uint64 decodedPoolId, bytes16 decodedTrancheId, bytes32 decodedInvestor, uint128 decodedToken, uint128 decodedAmount - ) = ConnectorMessages.parseDecreaseRedeemOrder(_message.ref(0)); + ) = Messages.parseDecreaseRedeemOrder(_message.ref(0)); assertEq(uint256(decodedPoolId), uint256(poolId)); assertEq(decodedTrancheId, trancheId); @@ -549,52 +519,60 @@ contract MessagesTest is Test { uint64 poolId = 1; bytes16 trancheId = bytes16(hex"811acd5b3f17c06841c7e41e9e04cb1b"); bytes32 investor = bytes32(0x4564564564564564564564564564564564564564564564564564564564564564); + uint128 currency = 246803579; + bytes memory expectedHex = - hex"0d0000000000000001811acd5b3f17c06841c7e41e9e04cb1b4564564564564564564564564564564564564564564564564564564564564564"; + hex"0d0000000000000001811acd5b3f17c06841c7e41e9e04cb1b45645645645645645645645645645645645645645645645645645645645645640000000000000000000000000eb5ec7b"; - assertEq(ConnectorMessages.formatCollectInvest(poolId, trancheId, investor), expectedHex); + assertEq(Messages.formatCollectInvest(poolId, trancheId, investor, currency), expectedHex); - (uint64 decodedPoolId, bytes16 decodedTrancheId, bytes32 decodedInvestor) = - ConnectorMessages.parseCollectInvest(expectedHex.ref(0)); + (uint64 decodedPoolId, bytes16 decodedTrancheId, bytes32 decodedInvestor, uint128 decodedCurrency) = + Messages.parseCollectInvest(expectedHex.ref(0)); assertEq(uint256(decodedPoolId), poolId); assertEq(decodedTrancheId, trancheId); assertEq(decodedInvestor, investor); + assertEq(decodedCurrency, currency); } - function testCollectInvestEquivalence(uint64 poolId, bytes16 trancheId, bytes32 user) public { - bytes memory _message = ConnectorMessages.formatCollectInvest(poolId, trancheId, user); - (uint64 decodedPoolId, bytes16 decodedTrancheId, bytes32 decodedUser) = - ConnectorMessages.parseCollectInvest(_message.ref(0)); + function testCollectInvestEquivalence(uint64 poolId, bytes16 trancheId, bytes32 user, uint128 currency) public { + bytes memory _message = Messages.formatCollectInvest(poolId, trancheId, user, currency); + (uint64 decodedPoolId, bytes16 decodedTrancheId, bytes32 decodedUser, uint128 decodedCurrency) = + Messages.parseCollectInvest(_message.ref(0)); assertEq(uint256(decodedPoolId), uint256(poolId)); assertEq(decodedTrancheId, trancheId); assertEq(decodedUser, user); + assertEq(decodedCurrency, currency); } function testCollectRedeem() public { uint64 poolId = 12378532; bytes16 trancheId = bytes16(hex"811acd5b3f17c06841c7e41e9e04cb1b"); bytes32 investor = bytes32(0x4564564564564564564564564564564564564564564564564564564564564564); + uint128 currency = 246803579; + bytes memory expectedHex = - hex"0e0000000000bce1a4811acd5b3f17c06841c7e41e9e04cb1b4564564564564564564564564564564564564564564564564564564564564564"; + hex"0e0000000000bce1a4811acd5b3f17c06841c7e41e9e04cb1b45645645645645645645645645645645645645645645645645645645645645640000000000000000000000000eb5ec7b"; - assertEq(ConnectorMessages.formatCollectRedeem(poolId, trancheId, investor), expectedHex); + assertEq(Messages.formatCollectRedeem(poolId, trancheId, investor, currency), expectedHex); - (uint64 decodedPoolId, bytes16 decodedTrancheId, bytes32 decodedInvestor) = - ConnectorMessages.parseCollectRedeem(expectedHex.ref(0)); + (uint64 decodedPoolId, bytes16 decodedTrancheId, bytes32 decodedInvestor, uint128 decodedCurrency) = + Messages.parseCollectRedeem(expectedHex.ref(0)); assertEq(uint256(decodedPoolId), poolId); assertEq(decodedTrancheId, trancheId); assertEq(decodedInvestor, investor); + assertEq(decodedCurrency, currency); } - function testCollectRedeemEquivalence(uint64 poolId, bytes16 trancheId, bytes32 user) public { - bytes memory _message = ConnectorMessages.formatCollectRedeem(poolId, trancheId, user); - (uint64 decodedPoolId, bytes16 decodedTrancheId, bytes32 decodedUser) = - ConnectorMessages.parseCollectRedeem(_message.ref(0)); + function testCollectRedeemEquivalence(uint64 poolId, bytes16 trancheId, bytes32 user, uint128 currency) public { + bytes memory _message = Messages.formatCollectRedeem(poolId, trancheId, user, currency); + (uint64 decodedPoolId, bytes16 decodedTrancheId, bytes32 decodedUser, uint128 decodedCurrency) = + Messages.parseCollectRedeem(_message.ref(0)); assertEq(uint256(decodedPoolId), uint256(poolId)); assertEq(decodedTrancheId, trancheId); assertEq(decodedUser, user); + assertEq(decodedCurrency, currency); } function testExecutedDecreaseInvestOrder() public { @@ -603,13 +581,12 @@ contract MessagesTest is Test { bytes32 investor = bytes32(0x1231231231231231231231231231231231231231000000000000000000000000); uint128 currency = 246803579; uint128 currencyPayout = 50000000000000000000000000; - uint128 remainingInvestOrder = 200000000000000000000000000; bytes memory expectedHex = - hex"0f0000000000bce1a4811acd5b3f17c06841c7e41e9e04cb1b12312312312312312312312312312312312312310000000000000000000000000000000000000000000000000eb5ec7b0000000000295be96e640669720000000000000000a56fa5b99019a5c8000000"; + hex"0f0000000000bce1a4811acd5b3f17c06841c7e41e9e04cb1b12312312312312312312312312312312312312310000000000000000000000000000000000000000000000000eb5ec7b0000000000295be96e64066972000000"; assertEq( - ConnectorMessages.formatExecutedDecreaseInvestOrder( - poolId, trancheId, investor, currency, currencyPayout, remainingInvestOrder + Messages.formatExecutedDecreaseInvestOrder( + poolId, trancheId, investor, currency, currencyPayout ), expectedHex ); @@ -617,17 +594,15 @@ contract MessagesTest is Test { ( uint64 decodedPoolId, bytes16 decodedTrancheId, - bytes32 decodedInvestor, + address decodedInvestor, uint128 decodedCurrency, - uint128 decodedCurrencyPayout, - uint128 decodedRemainingInvestOrder - ) = ConnectorMessages.parseExecutedDecreaseInvestOrder(expectedHex.ref(0)); + uint128 decodedCurrencyPayout + ) = Messages.parseExecutedDecreaseInvestOrder(expectedHex.ref(0)); assertEq(uint256(decodedPoolId), poolId); assertEq(decodedTrancheId, trancheId); - assertEq(decodedInvestor, investor); + assertEq(decodedInvestor, address(bytes20(investor))); assertEq(decodedCurrency, currency); assertEq(decodedCurrencyPayout, currencyPayout); - assertEq(decodedRemainingInvestOrder, remainingInvestOrder); } function testExecutedDecreaseInvestOrderEquivalence( @@ -635,27 +610,24 @@ contract MessagesTest is Test { bytes16 trancheId, bytes32 investor, uint128 currency, - uint128 currencyPayout, - uint128 remainingInvestOrder + uint128 currencyPayout ) public { - bytes memory _message = ConnectorMessages.formatExecutedDecreaseInvestOrder( - poolId, trancheId, investor, currency, currencyPayout, remainingInvestOrder + bytes memory _message = Messages.formatExecutedDecreaseInvestOrder( + poolId, trancheId, investor, currency, currencyPayout ); ( uint64 decodedPoolId, bytes16 decodedTrancheId, - bytes32 decodedInvestor, + address decodedInvestor, uint128 decodedCurrency, - uint128 decodedCurrencyPayout, - uint128 decodedRemainingInvestOrder - ) = ConnectorMessages.parseExecutedDecreaseInvestOrder(_message.ref(0)); + uint128 decodedCurrencyPayout + ) = Messages.parseExecutedDecreaseInvestOrder(_message.ref(0)); assertEq(uint256(decodedPoolId), uint256(poolId)); assertEq(decodedTrancheId, trancheId); - assertEq(decodedInvestor, investor); + assertEq(decodedInvestor, address(bytes20(investor))); assertEq(decodedCurrency, currency); assertEq(decodedCurrencyPayout, currencyPayout); - assertEq(decodedRemainingInvestOrder, remainingInvestOrder); } function testExecutedDecreaseRedeemOrder() public { @@ -664,14 +636,13 @@ contract MessagesTest is Test { bytes32 investor = bytes32(0x1231231231231231231231231231231231231231000000000000000000000000); uint128 currency = 246803579; uint128 currencyPayout = 50000000000000000000000000; - uint128 remainingRedeemOrder = 200000000000000000000000000; - + bytes memory expectedHex = - hex"100000000000bce1a4811acd5b3f17c06841c7e41e9e04cb1b12312312312312312312312312312312312312310000000000000000000000000000000000000000000000000eb5ec7b0000000000295be96e640669720000000000000000a56fa5b99019a5c8000000"; + hex"100000000000bce1a4811acd5b3f17c06841c7e41e9e04cb1b12312312312312312312312312312312312312310000000000000000000000000000000000000000000000000eb5ec7b0000000000295be96e64066972000000"; assertEq( - ConnectorMessages.formatExecutedDecreaseRedeemOrder( - poolId, trancheId, investor, currency, currencyPayout, remainingRedeemOrder + Messages.formatExecutedDecreaseRedeemOrder( + poolId, trancheId, investor, currency, currencyPayout ), expectedHex ); @@ -679,17 +650,15 @@ contract MessagesTest is Test { ( uint64 decodedPoolId, bytes16 decodedTrancheId, - bytes32 decodedInvestor, + address decodedInvestor, uint128 decodedCurrency, - uint128 decodedCurrencyPayout, - uint128 decodedRemainingRedeemOrder - ) = ConnectorMessages.parseExecutedDecreaseRedeemOrder(expectedHex.ref(0)); + uint128 decodedCurrencyPayout + ) = Messages.parseExecutedDecreaseRedeemOrder(expectedHex.ref(0)); assertEq(uint256(decodedPoolId), poolId); assertEq(decodedTrancheId, trancheId); - assertEq(decodedInvestor, investor); + assertEq(bytes32(bytes20(decodedInvestor)), investor); assertEq(decodedCurrency, currency); assertEq(decodedCurrencyPayout, currencyPayout); - assertEq(decodedRemainingRedeemOrder, remainingRedeemOrder); } function testExecutedDecreaseRedeemOrderEquivalence( @@ -697,50 +666,47 @@ contract MessagesTest is Test { bytes16 trancheId, bytes32 investor, uint128 currency, - uint128 currencyPayout, - uint128 remainingRedeemOrder + uint128 currencyPayout ) public { - bytes memory _message = ConnectorMessages.formatExecutedDecreaseRedeemOrder( - poolId, trancheId, investor, currency, currencyPayout, remainingRedeemOrder + bytes memory _message = Messages.formatExecutedDecreaseRedeemOrder( + poolId, trancheId, investor, currency, currencyPayout ); ( uint64 decodedPoolId, bytes16 decodedTrancheId, - bytes32 decodedInvestor, + address decodedInvestor, uint128 decodedCurrency, - uint128 decodedCurrencyPayout, - uint128 decodedRemainingRedeemOrder - ) = ConnectorMessages.parseExecutedDecreaseRedeemOrder(_message.ref(0)); + uint128 decodedCurrencyPayout + ) = Messages.parseExecutedDecreaseRedeemOrder(_message.ref(0)); assertEq(uint256(decodedPoolId), uint256(poolId)); assertEq(decodedTrancheId, trancheId); - assertEq(decodedInvestor, investor); + assertEq(decodedInvestor, address(bytes20(investor))); assertEq(decodedCurrency, currency); assertEq(decodedCurrencyPayout, currencyPayout); - assertEq(decodedRemainingRedeemOrder, remainingRedeemOrder); } function testExecutedCollectInvest() public { - uint64 poolId = 12378532; + uint64 poolId = 12378532; bytes16 trancheId = bytes16(hex"811acd5b3f17c06841c7e41e9e04cb1b"); bytes32 investor = bytes32(0x1231231231231231231231231231231231231231000000000000000000000000); uint128 currency = 246803579; uint128 currencyPayout = 100000000000000000000000000; uint128 trancheTokensPayout = 50000000000000000000000000; - uint128 remainingInvestOrder = 300000000000000000000000000; + bytes memory expectedHex = - hex"110000000000bce1a4811acd5b3f17c06841c7e41e9e04cb1b12312312312312312312312312312312312312310000000000000000000000000000000000000000000000000eb5ec7b000000000052b7d2dcc80cd2e40000000000000000295be96e640669720000000000000000f8277896582678ac000000"; + hex"110000000000bce1a4811acd5b3f17c06841c7e41e9e04cb1b12312312312312312312312312312312312312310000000000000000000000000000000000000000000000000eb5ec7b000000000052b7d2dcc80cd2e40000000000000000295be96e64066972000000"; assertEq( - ConnectorMessages.formatExecutedCollectInvest( - poolId, trancheId, investor, currency, currencyPayout, trancheTokensPayout, remainingInvestOrder + Messages.formatExecutedCollectInvest( + poolId, trancheId, investor, currency, currencyPayout, trancheTokensPayout ), expectedHex ); // separate asserts into two functions to avoid stack too deep error testParseExecutedCollectInvestPart1(expectedHex, poolId, trancheId, investor, currency); - testParseExecutedCollectInvestPart2(expectedHex, currencyPayout, trancheTokensPayout, remainingInvestOrder); + testParseExecutedCollectInvestPart2(expectedHex, currencyPayout, trancheTokensPayout); } function testParseExecutedCollectInvestPart1( @@ -750,27 +716,25 @@ contract MessagesTest is Test { bytes32 investor, uint128 currency ) internal { - (uint64 decodedPoolId, bytes16 decodedTrancheId, bytes32 decodedInvestor, uint128 decodedCurrency,,,) = - ConnectorMessages.parseExecutedCollectInvest(expectedHex.ref(0)); + (uint64 decodedPoolId, bytes16 decodedTrancheId, address decodedInvestor, uint128 decodedCurrency,,) = + Messages.parseExecutedCollectInvest(expectedHex.ref(0)); assertEq(decodedPoolId, poolId); assertEq(decodedTrancheId, trancheId); - assertEq(decodedInvestor, investor); + assertEq(decodedInvestor, address(bytes20(investor))); assertEq(decodedCurrency, currency); } function testParseExecutedCollectInvestPart2( bytes memory expectedHex, uint128 currencyPayout, - uint128 trancheTokensPayout, - uint128 remainingInvestOrder + uint128 trancheTokensPayout ) internal { - (,,,, uint128 decodedcurrencyPayout, uint128 decodedTrancheTokensPayout, uint128 decodedRemainingInvestOrder) = - ConnectorMessages.parseExecutedCollectInvest(expectedHex.ref(0)); + (,,,, uint128 decodedcurrencyPayout, uint128 decodedTrancheTokensPayout) = + Messages.parseExecutedCollectInvest(expectedHex.ref(0)); assertEq(decodedcurrencyPayout, currencyPayout); assertEq(decodedTrancheTokensPayout, trancheTokensPayout); - assertEq(decodedRemainingInvestOrder, remainingInvestOrder); } function testExecutedCollectInvestEquivalence( @@ -779,15 +743,14 @@ contract MessagesTest is Test { bytes32 investor, uint128 currency, uint128 currencyPayout, - uint128 trancheTokensPayout, - uint128 remainingInvestOrder + uint128 trancheTokensPayout ) public { - bytes memory _message = ConnectorMessages.formatExecutedCollectInvest( - poolId, trancheId, investor, currency, currencyPayout, trancheTokensPayout, remainingInvestOrder + bytes memory _message = Messages.formatExecutedCollectInvest( + poolId, trancheId, investor, currency, currencyPayout, trancheTokensPayout ); // separate asserts into two functions to avoid stack too deep error testParseExecutedCollectInvestPart1(_message, poolId, trancheId, investor, currency); - testParseExecutedCollectInvestPart2(_message, currencyPayout, trancheTokensPayout, remainingInvestOrder); + testParseExecutedCollectInvestPart2(_message, currencyPayout, trancheTokensPayout); } function testExecutedCollectRedeem() public { @@ -797,20 +760,19 @@ contract MessagesTest is Test { uint128 currency = 246803579; uint128 currencyPayout = 100000000000000000000000000; uint128 trancheTokensRedeemed = 50000000000000000000000000; - uint128 remainingRedeemOrder = 300000000000000000000000000; bytes memory expectedHex = - hex"120000000000bce1a4811acd5b3f17c06841c7e41e9e04cb1b12312312312312312312312312312312312312310000000000000000000000000000000000000000000000000eb5ec7b000000000052b7d2dcc80cd2e40000000000000000295be96e640669720000000000000000f8277896582678ac000000"; + hex"120000000000bce1a4811acd5b3f17c06841c7e41e9e04cb1b12312312312312312312312312312312312312310000000000000000000000000000000000000000000000000eb5ec7b000000000052b7d2dcc80cd2e40000000000000000295be96e64066972000000"; assertEq( - ConnectorMessages.formatExecutedCollectRedeem( - poolId, trancheId, investor, currency, currencyPayout, trancheTokensRedeemed, remainingRedeemOrder + Messages.formatExecutedCollectRedeem( + poolId, trancheId, investor, currency, currencyPayout, trancheTokensRedeemed ), expectedHex ); // separate asserts into two functions to avoid stack too deep error testParseExecutedCollectRedeemPart1(expectedHex, poolId, trancheId, investor, currency); - testParseExecutedCollectRedeemPart2(expectedHex, currencyPayout, trancheTokensRedeemed, remainingRedeemOrder); + testParseExecutedCollectRedeemPart2(expectedHex, currencyPayout, trancheTokensRedeemed); } function testExecutedCollectRedeemEquivalence( @@ -819,15 +781,14 @@ contract MessagesTest is Test { bytes32 investor, uint128 currency, uint128 currencyPayout, - uint128 trancheTokensRedeemed, - uint128 remainingRedeemOrder + uint128 trancheTokensRedeemed ) public { - bytes memory _message = ConnectorMessages.formatExecutedCollectRedeem( - poolId, trancheId, investor, currency, currencyPayout, trancheTokensRedeemed, remainingRedeemOrder + bytes memory _message = Messages.formatExecutedCollectRedeem( + poolId, trancheId, investor, currency, currencyPayout, trancheTokensRedeemed ); // separate asserts into two functions to avoid stack too deep error testParseExecutedCollectRedeemPart1(_message, poolId, trancheId, investor, currency); - testParseExecutedCollectRedeemPart2(_message, currencyPayout, trancheTokensRedeemed, remainingRedeemOrder); + testParseExecutedCollectRedeemPart2(_message, currencyPayout, trancheTokensRedeemed); } function testParseExecutedCollectRedeemPart1( @@ -837,43 +798,42 @@ contract MessagesTest is Test { bytes32 investor, uint128 currency ) internal { - (uint64 decodedPoolId, bytes16 decodedTrancheId, bytes32 decodedInvestor, uint128 decodedCurrency,,,) = - ConnectorMessages.parseExecutedCollectRedeem(expectedHex.ref(0)); + (uint64 decodedPoolId, bytes16 decodedTrancheId, address decodedInvestor, uint128 decodedCurrency,,) = + Messages.parseExecutedCollectRedeem(expectedHex.ref(0)); assertEq(decodedPoolId, poolId); assertEq(decodedTrancheId, trancheId); - assertEq(decodedInvestor, investor); + + assertEq(decodedInvestor, address(bytes20(investor))); assertEq(decodedCurrency, currency); } function testParseExecutedCollectRedeemPart2( bytes memory expectedHex, uint128 currencyPayout, - uint128 trancheTokensRedeemed, - uint128 remainingRedeemOrder + uint128 trancheTokensRedeemed ) internal { - (,,,, uint128 decodedCurrencyPayout, uint128 decodedTrancheTokensRedeemed, uint128 decodedRemainingRedeemOrder) - = ConnectorMessages.parseExecutedCollectRedeem(expectedHex.ref(0)); + (,,,, uint128 decodedCurrencyPayout, uint128 decodedTrancheTokensRedeemed) + = Messages.parseExecutedCollectRedeem(expectedHex.ref(0)); assertEq(decodedCurrencyPayout, currencyPayout); assertEq(decodedTrancheTokensRedeemed, trancheTokensRedeemed); - assertEq(decodedRemainingRedeemOrder, remainingRedeemOrder); } function testFormatDomainCentrifuge() public { - assertEq(ConnectorMessages.formatDomain(ConnectorMessages.Domain.Centrifuge), hex"000000000000000000"); + assertEq(Messages.formatDomain(Messages.Domain.Centrifuge), hex"000000000000000000"); } function testFormatDomainMoonbeam() public { - assertEq(ConnectorMessages.formatDomain(ConnectorMessages.Domain.EVM, 1284), hex"010000000000000504"); + assertEq(Messages.formatDomain(Messages.Domain.EVM, 1284), hex"010000000000000504"); } function testFormatDomainMoonbaseAlpha() public { - assertEq(ConnectorMessages.formatDomain(ConnectorMessages.Domain.EVM, 1287), hex"010000000000000507"); + assertEq(Messages.formatDomain(Messages.Domain.EVM, 1287), hex"010000000000000507"); } function testFormatDomainAvalanche() public { - assertEq(ConnectorMessages.formatDomain(ConnectorMessages.Domain.EVM, 43114), hex"01000000000000a86a"); + assertEq(Messages.formatDomain(Messages.Domain.EVM, 43114), hex"01000000000000a86a"); } // Convert an hexadecimal character to their value diff --git a/test/Misc.t.sol b/test/Misc.t.sol index 40d89dee..93ff19c2 100644 --- a/test/Misc.t.sol +++ b/test/Misc.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.18; pragma abicoder v2; import {TypedMemView} from "memview-sol/TypedMemView.sol"; -import {ConnectorMessages} from "src/Messages.sol"; +import {Messages} from "src/Messages.sol"; import "forge-std/Test.sol"; /// A place for Misc-like tests diff --git a/test/accounts/PoolManager.sol b/test/accounts/PoolManager.sol index 897597e6..480a30db 100644 --- a/test/accounts/PoolManager.sol +++ b/test/accounts/PoolManager.sol @@ -2,29 +2,29 @@ pragma solidity ^0.8.18; pragma abicoder v2; -import {MockHomeConnector} from "../mock/MockHomeConnector.sol"; +import {MockHomeLiquidityPools} from "../mock/MockHomeLiquidityPools.sol"; import "forge-std/Test.sol"; contract InvariantPoolManager is Test { - MockHomeConnector connector; + MockHomeLiquidityPools homePools; uint64[] public allPools; bytes16[] public allTranches; mapping(bytes16 => uint64) public trancheIdToPoolId; - constructor(MockHomeConnector connector_) { - connector = connector_; + constructor(MockHomeLiquidityPools homePools_) { + homePools = homePools_; } function addPool(uint64 poolId) public { - connector.addPool(poolId); + homePools.addPool(poolId); allPools.push(poolId); } function addPoolAndTranche(uint64 poolId, bytes16 trancheId, uint8 decimals, uint128 price) public { addPool(poolId); - connector.addTranche(poolId, trancheId, "-", "-", decimals, price); + homePools.addTranche(poolId, trancheId, "-", "-", decimals, price); allTranches.push(trancheId); trancheIdToPoolId[trancheId] = poolId; diff --git a/test/mock/MockHomeConnector.sol b/test/mock/MockHomeLiquidityPools.sol similarity index 61% rename from test/mock/MockHomeConnector.sol rename to test/mock/MockHomeLiquidityPools.sol index aeb162a2..0616f7b6 100644 --- a/test/mock/MockHomeConnector.sol +++ b/test/mock/MockHomeLiquidityPools.sol @@ -3,19 +3,19 @@ pragma solidity ^0.8.18; pragma abicoder v2; import {TypedMemView} from "memview-sol/TypedMemView.sol"; -import {ConnectorMessages} from "src/Messages.sol"; +import {Messages} from "src/Messages.sol"; import "forge-std/Test.sol"; -import {ConnectorXCMRouter} from "src/routers/xcm/Router.sol"; +import {XCMRouter} from "src/routers/xcm/Router.sol"; interface XcmRouterLike { function handle(bytes memory _message) external; function send(bytes memory message) external; } -contract MockHomeConnector is Test { +contract MockHomeLiquidityPools is Test { using TypedMemView for bytes; using TypedMemView for bytes29; - using ConnectorMessages for bytes29; + using Messages for bytes29; XcmRouterLike public immutable router; @@ -35,17 +35,17 @@ contract MockHomeConnector is Test { } function addCurrency(uint128 currency, address currencyAddress) public { - bytes memory _message = ConnectorMessages.formatAddCurrency(currency, currencyAddress); + bytes memory _message = Messages.formatAddCurrency(currency, currencyAddress); router.handle(_message); } function addPool(uint64 poolId) public { - bytes memory _message = ConnectorMessages.formatAddPool(poolId); + bytes memory _message = Messages.formatAddPool(poolId); router.handle(_message); } function allowPoolCurrency(uint64 poolId, uint128 currency) public { - bytes memory _message = ConnectorMessages.formatAllowPoolCurrency(poolId, currency); + bytes memory _message = Messages.formatAllowPoolCurrency(poolId, currency); router.handle(_message); } @@ -58,23 +58,23 @@ contract MockHomeConnector is Test { uint128 price ) public { bytes memory _message = - ConnectorMessages.formatAddTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); + Messages.formatAddTranche(poolId, trancheId, tokenName, tokenSymbol, decimals, price); router.handle(_message); } function updateMember(uint64 poolId, bytes16 trancheId, address user, uint64 validUntil) public { - bytes memory _message = ConnectorMessages.formatUpdateMember(poolId, trancheId, user, validUntil); + bytes memory _message = Messages.formatUpdateMember(poolId, trancheId, user, validUntil); router.handle(_message); } function updateTokenPrice(uint64 poolId, bytes16 trancheId, uint128 price) public { - bytes memory _message = ConnectorMessages.formatUpdateTrancheTokenPrice(poolId, trancheId, price); + bytes memory _message = Messages.formatUpdateTrancheTokenPrice(poolId, trancheId, price); router.handle(_message); } // Trigger an incoming (e.g. Centrifuge Chain -> EVM) transfer of stable coins function incomingTransfer(uint128 currency, bytes32 sender, bytes32 recipient, uint128 amount) public { - bytes memory _message = ConnectorMessages.formatTransfer(currency, sender, recipient, amount); + bytes memory _message = Messages.formatTransfer(currency, sender, recipient, amount); router.handle(_message); } @@ -87,11 +87,11 @@ contract MockHomeConnector is Test { address destinationAddress, uint128 amount ) public { - bytes memory _message = ConnectorMessages.formatTransferTrancheTokens( + bytes memory _message = Messages.formatTransferTrancheTokens( poolId, trancheId, bytes32(bytes20(msg.sender)), - ConnectorMessages.formatDomain(ConnectorMessages.Domain.EVM, destinationChainId), + Messages.formatDomain(Messages.Domain.EVM, destinationChainId), currencyId, destinationAddress, amount @@ -100,10 +100,37 @@ contract MockHomeConnector is Test { } function incomingScheduleRely(address spell) public { - bytes memory _message = ConnectorMessages.formatAddAdmin(spell); + bytes memory _message = Messages.formatAddAdmin(spell); router.handle(_message); } + function isExecutedCollectInvest( + uint64 poolId, + bytes16 trancheId, + bytes32 investor, + uint128 currency, + uint128 currencyPayout, + uint128 trancheTokensPayout + ) public { + bytes memory _message = Messages.formatExecutedCollectInvest( + poolId, trancheId, investor, currency, currencyPayout, trancheTokensPayout); + router.handle(_message); + } + + function isExecutedCollectRedeem( + uint64 poolId, + bytes16 trancheId, + bytes32 investor, + uint128 currency, + uint128 currencyPayout, + uint128 trancheTokensPayout + ) public { + bytes memory _message = Messages.formatExecutedCollectRedeem( + poolId, trancheId, investor, currency, currencyPayout, trancheTokensPayout); + router.handle(_message); + } + + function dispatch( uint32 _destinationDomain, uint256 _destinationChainId, diff --git a/test/mock/MockXcmRouter.sol b/test/mock/MockXcmRouter.sol index 4ae9ce91..97afeb88 100644 --- a/test/mock/MockXcmRouter.sol +++ b/test/mock/MockXcmRouter.sol @@ -4,9 +4,9 @@ pragma abicoder v2; import {TypedMemView} from "memview-sol/TypedMemView.sol"; import "forge-std/Test.sol"; -import {CentrifugeConnector} from "src/Connector.sol"; -import {ConnectorMessages} from "src/Messages.sol"; -import {ConnectorGateway} from "src/routers/Gateway.sol"; +import {InvestmentManager} from "src/InvestmentManager.sol"; +import {Messages} from "src/Messages.sol"; +import {Gateway} from "src/Gateway.sol"; contract MockXcmRouter is Test { using TypedMemView for bytes; @@ -40,7 +40,7 @@ contract MockXcmRouter is Test { } function handle(bytes memory _message) external { - ConnectorGateway(gateway).handle(_message); + Gateway(gateway).handle(_message); } function send(bytes memory message) public onlyGateway { From 3eba82fab50df78aaac7dc99fd757b3c5b3f29d1 Mon Sep 17 00:00:00 2001 From: ilinzweilin Date: Wed, 16 Aug 2023 18:08:47 +0100 Subject: [PATCH 23/24] remove test env --- src/Gateway.sol | 3 +-- src/InvestmentManager.sol | 5 +---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Gateway.sol b/src/Gateway.sol index 824735cc..3474c627 100644 --- a/src/Gateway.sol +++ b/src/Gateway.sol @@ -4,7 +4,6 @@ pragma abicoder v2; import {TypedMemView} from "memview-sol/TypedMemView.sol"; import {Messages} from "./Messages.sol"; -import "forge-std/Test.sol"; interface InvestmentManagerLike { function addCurrency(uint128 currency, address currencyAddress) external; @@ -63,7 +62,7 @@ interface AuthLike { function rely(address usr) external; } -contract Gateway is Test { +contract Gateway { using TypedMemView for bytes; // why bytes29? - https://github.com/summa-tx/memview-sol#why-bytes29 using TypedMemView for bytes29; diff --git a/src/InvestmentManager.sol b/src/InvestmentManager.sol index 78fbf05f..a29ebed3 100644 --- a/src/InvestmentManager.sol +++ b/src/InvestmentManager.sol @@ -7,7 +7,6 @@ import { LiquidityPool } from "./liquidityPool/LiquidityPool.sol"; import { ERC20Like } from "./token/restricted.sol"; import { MemberlistLike } from "./token/memberlist.sol"; import "./auth/auth.sol"; -import "forge-std/Test.sol"; interface GatewayLike { function transferTrancheTokensToCentrifuge( @@ -89,7 +88,7 @@ struct LPValues { uint128 maxRedeem; } -contract InvestmentManager is Auth, Test { +contract InvestmentManager is Auth { mapping(uint64 => Pool) public pools; // pools on centrifuge chain mapping(uint64 => mapping(bytes16 => Tranche)) public tranches; // centrifuge chain tranches @@ -543,7 +542,6 @@ contract InvestmentManager is Auth, Test { function processMint(address _user, uint256 _trancheTokenAmount) poolActive gatewayActive public onlyLiquidityPoolWard returns (uint256) { address _liquidityPool = msg.sender; uint128 trancheTokenAmount = _toUint128(_trancheTokenAmount); - console.logUint(trancheTokenAmount); LiquidityPoolLike lPool = LiquidityPoolLike(_liquidityPool); require((trancheTokenAmount <= orderbook[_user][ _liquidityPool].maxMint), "InvestmentManager/amount-exceeds-mint-limits"); @@ -551,7 +549,6 @@ contract InvestmentManager is Auth, Test { uint128 userTrancheTokenPriceLP = calcDepositTrancheTokenPrice(_user, _liquidityPool); require((userTrancheTokenPriceLP > 0), "LiquidityPool/amount-exceeds-mint-limits"); uint128 currencyAmount = trancheTokenAmount * userTrancheTokenPriceLP; - //console.logUint( currencyAmount); _decreaseDepositLimits(_user, _liquidityPool, currencyAmount, trancheTokenAmount); // decrease the possible deposit limits require(lPool.hasMember( _user), "InvestmentManager/trancheTokens-not-a-member"); require(lPool.transferFrom(address(escrow), _user, trancheTokenAmount), "InvestmentManager/trancheTokens-transfer-failed"); From 58ac242f5d657d7fda4a62a52dfec2aca76952e3 Mon Sep 17 00:00:00 2001 From: ilinzweilin Date: Thu, 17 Aug 2023 13:17:52 +0100 Subject: [PATCH 24/24] sign commit --- test/Admin.t.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/Admin.t.sol b/test/Admin.t.sol index 8b1c373e..8e38c27f 100644 --- a/test/Admin.t.sol +++ b/test/Admin.t.sol @@ -227,11 +227,12 @@ contract AdminTest is Test { assertEq(gateway.wards(spell), 1); } + function testShortRelyFailsBefore24hours() public { address spell = vm.addr(1); centChainLiquidityPools.incomingScheduleUpgrade(spell); vm.warp(block.timestamp + shortWait - 1 hours); - vm.expectRevert("Gateway/user-not-ready"); + vm.expectRevert("Gateway/user--not-ready"); gateway.executeScheduledRely(spell); }