Skip to content

Commit

Permalink
Merge branch 'tests' into migration-contracts
Browse files Browse the repository at this point in the history
  • Loading branch information
AStox committed Oct 24, 2023
2 parents 7c08158 + 17f202a commit f1b721c
Show file tree
Hide file tree
Showing 8 changed files with 440 additions and 125 deletions.
58 changes: 29 additions & 29 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -113,33 +113,33 @@ jobs:
coverage-files: ./lcov.info
minimum-coverage: 60 # Set coverage threshold.

slither-analyze:
runs-on: "ubuntu-latest"
permissions:
actions: "read"
contents: "read"
security-events: "write"
steps:
- name: "Check out the repo"
uses: "actions/checkout@v3"
with:
submodules: "recursive"

- name: "Run Slither analysis"
uses: "crytic/[email protected]"
id: "slither"
with:
fail-on: "none"
sarif: "results.sarif"
solc-version: "0.8.21"
target: "src/"

- name: Upload SARIF file
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: ${{ steps.slither.outputs.sarif }}
# slither-analyze:
# runs-on: "ubuntu-latest"
# permissions:
# actions: "read"
# contents: "read"
# security-events: "write"
# steps:
# - name: "Check out the repo"
# uses: "actions/checkout@v3"
# with:
# submodules: "recursive"

# - name: "Run Slither analysis"
# uses: "crytic/[email protected]"
# id: "slither"
# with:
# fail-on: "none"
# sarif: "results.sarif"
# solc-version: "0.8.21"
# target: "src/"

# - name: Upload SARIF file
# uses: github/codeql-action/upload-sarif@v2
# with:
# sarif_file: ${{ steps.slither.outputs.sarif }}

- name: "Add Slither summary"
run: |
echo "## Slither result" >> $GITHUB_STEP_SUMMARY
echo "✅ Uploaded to GitHub code scanning" >> $GITHUB_STEP_SUMMARY
# - name: "Add Slither summary"
# run: |
# echo "## Slither result" >> $GITHUB_STEP_SUMMARY
# echo "✅ Uploaded to GitHub code scanning" >> $GITHUB_STEP_SUMMARY
72 changes: 52 additions & 20 deletions src/InvestmentManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -368,11 +368,14 @@ contract InvestmentManager is Auth {
// Calculating the price with both payouts as currencyPayout
// leads to an effective redeem price of 1.0 and thus the user actually receiving
// exactly currencyPayout on both deposit() and mint()
(uint8 currencyDecimals, uint8 trancheTokenDecimals) = _getPoolDecimals(liquidityPool);
uint256 currencyPayoutInPriceDecimals = _toPriceDecimals(currencyPayout, currencyDecimals);
state.redeemPrice = _calculatePrice(
liquidityPool,
state.maxWithdraw + currencyPayout,
((maxRedeem(liquidityPool, user)) + currencyPayout).toUint128()
_toPriceDecimals(state.maxWithdraw, currencyDecimals) + currencyPayoutInPriceDecimals,
_toPriceDecimals(maxRedeem(liquidityPool, user).toUint128(), trancheTokenDecimals)
+ currencyPayoutInPriceDecimals
);

state.maxWithdraw = state.maxWithdraw + currencyPayout;
state.remainingDepositRequest = remainingInvestOrder;

Expand All @@ -394,14 +397,19 @@ contract InvestmentManager is Auth {
uint128 remainingRedeemOrder
) public onlyGateway {
address liquidityPool = poolManager.getLiquidityPool(poolId, trancheId, currencyId);
InvestmentState storage state = investments[liquidityPool][user];

// Calculating the price with both payouts as trancheTokenPayout
// leads to an effective redeem price of 1.0 and thus the user actually receiving
// exactly trancheTokenPayout on both deposit() and mint()
InvestmentState storage state = investments[liquidityPool][user];
(uint8 currencyDecimals, uint8 trancheTokenDecimals) = _getPoolDecimals(liquidityPool);
uint256 trancheTokenPayoutInPriceDecimals = _toPriceDecimals(trancheTokenPayout, trancheTokenDecimals);
state.depositPrice = _calculatePrice(
liquidityPool, _maxDeposit(liquidityPool, user) + trancheTokenPayout, state.maxMint + trancheTokenPayout
_toPriceDecimals(_maxDeposit(liquidityPool, user), currencyDecimals).toUint128()
+ trancheTokenPayoutInPriceDecimals,
_toPriceDecimals(state.maxMint, trancheTokenDecimals) + trancheTokenPayoutInPriceDecimals
);

state.maxMint = state.maxMint + trancheTokenPayout;
state.remainingRedeemRequest = remainingRedeemOrder;

Expand All @@ -415,19 +423,36 @@ contract InvestmentManager is Auth {
uint128 currencyId,
uint128 trancheTokenAmount
) public onlyGateway {
require(trancheTokenAmount != 0, "InvestmentManager/tranche-token-amount-is-zero");
address liquidityPool = poolManager.getLiquidityPool(poolId, trancheId, currencyId);
require(
_processRedeemRequest(liquidityPool, trancheTokenAmount, user), "InvestmentManager/failed-redeem-request"
);

// Transfer the tranche token amount from user to escrow (lock tranche tokens in escrow)
// If there's any unclaimed deposits, claim those first
InvestmentState storage state = investments[liquidityPool][user];
uint128 tokensToTransfer = trancheTokenAmount;
if (state.maxMint >= trancheTokenAmount) {
// The full redeem request is covered by the claimable amount
tokensToTransfer = 0;
state.maxMint = state.maxMint - trancheTokenAmount;
} else if (state.maxMint > 0) {
// The redeem request is only partially covered by the claimable amount
tokensToTransfer = trancheTokenAmount - state.maxMint;
state.maxMint = 0;
}

require(
AuthTransferLike(address(LiquidityPoolLike(liquidityPool).share())).authTransferFrom(
user, address(escrow), trancheTokenAmount
),
"InvestmentManager/transfer-failed"
_processRedeemRequest(liquidityPool, trancheTokenAmount, user), "InvestmentManager/failed-redeem-request"
);

// Transfer the tranche token amount that was not covered by tokens still in escrow for claims,
// from user to escrow (lock tranche tokens in escrow)
if (tokensToTransfer > 0) {
require(
AuthTransferLike(address(LiquidityPoolLike(liquidityPool).share())).authTransferFrom(
user, address(escrow), tokensToTransfer
),
"InvestmentManager/transfer-failed"
);
}
emit TriggerIncreaseRedeemOrder(poolId, trancheId, user, currencyId, trancheTokenAmount);
}

Expand Down Expand Up @@ -605,18 +630,25 @@ contract InvestmentManager is Auth {
}

function _calculatePrice(address liquidityPool, uint128 currencyAmount, uint128 trancheTokenAmount)
public
internal
view
returns (uint256 price)
{
if (currencyAmount == 0 || trancheTokenAmount == 0) {
return 0;
}

(uint8 currencyDecimals, uint8 trancheTokenDecimals) = _getPoolDecimals(liquidityPool);
price = _calculatePrice(
_toPriceDecimals(currencyAmount, currencyDecimals),
_toPriceDecimals(trancheTokenAmount, trancheTokenDecimals)
);
}

uint256 currencyAmountInPriceDecimals = _toPriceDecimals(currencyAmount, currencyDecimals);
uint256 trancheTokenAmountInPriceDecimals = _toPriceDecimals(trancheTokenAmount, trancheTokenDecimals);
function _calculatePrice(uint256 currencyAmountInPriceDecimals, uint256 trancheTokenAmountInPriceDecimals)
internal
view
returns (uint256 price)
{
if (currencyAmountInPriceDecimals == 0 || trancheTokenAmountInPriceDecimals == 0) {
return 0;
}

price = currencyAmountInPriceDecimals.mulDiv(
10 ** PRICE_DECIMALS, trancheTokenAmountInPriceDecimals, MathLib.Rounding.Down
Expand Down
104 changes: 48 additions & 56 deletions src/LiquidityPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,8 @@ pragma solidity 0.8.21;
import {Auth} from "./util/Auth.sol";
import {MathLib} from "./util/MathLib.sol";
import {SafeTransferLib} from "./util/SafeTransferLib.sol";
import {IERC20} from "./interfaces/IERC20.sol";
import {IERC4626} from "./interfaces/IERC4626.sol";

interface ERC20PermitLike {
function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)
external;
}

interface TrancheTokenLike is IERC20, ERC20PermitLike {}
import {IERC20, IERC20Metadata, IERC20Permit} from "./interfaces/IERC20.sol";
import {IERC7540} from "./interfaces/IERC7540.sol";

interface ManagerLike {
function deposit(address lp, uint256 assets, address receiver, address owner) external returns (uint256);
Expand All @@ -37,16 +30,16 @@ interface ManagerLike {

/// @title Liquidity Pool
/// @notice Liquidity Pool implementation for Centrifuge pools
/// following the EIP4626 standard, with asynchronous extension methods.
/// following the ERC-7540 Asynchronous Tokenized Vault standard
///
/// @dev Each Liquidity Pool is a tokenized vault issuing shares of Centrifuge tranches as restricted ERC20 tokens
/// @dev Each Liquidity Pool is a tokenized vault issuing shares of Centrifuge tranches as restricted ERC-20 tokens
/// against currency deposits based on the current share price.
///
/// This is extending the EIP4626 standard by 'requestDeposit' & 'requestRedeem' functions, where deposit and
/// redeem orders are submitted to the pools to be included in the execution of the following epoch. After
/// execution users can use the deposit, mint, redeem and withdraw functions to get their shares
/// ERC-7540 is an extension of the ERC-4626 standard by 'requestDeposit' & 'requestRedeem' methods, where
/// deposit and redeem orders are submitted to the pools to be included in the execution of the following epoch.
/// After execution users can use the deposit, mint, redeem and withdraw functions to get their shares
/// and/or assets from the pools.
contract LiquidityPool is Auth, IERC4626 {
contract LiquidityPool is Auth, IERC7540 {
using MathLib for uint256;

uint64 public immutable poolId;
Expand All @@ -62,7 +55,7 @@ contract LiquidityPool is Auth, IERC4626 {
/// @notice The restricted ERC-20 Liquidity Pool token. Has a ratio (token price) of underlying assets
/// exchanged on deposit/withdraw/redeem.
/// @dev Also known as tranche tokens.
TrancheTokenLike public immutable share;
IERC20Metadata public immutable share;

/// @notice Escrow contract for tokens
address public immutable escrow;
Expand All @@ -78,8 +71,6 @@ contract LiquidityPool is Auth, IERC4626 {

// --- Events ---
event File(bytes32 indexed what, address data);
event DepositRequest(address indexed sender, address indexed operator, uint256 assets);
event RedeemRequest(address indexed sender, address indexed operator, address indexed owner, uint256 shares);
event DecreaseDepositRequest(address indexed sender, uint256 assets);
event DecreaseRedeemRequest(address indexed sender, uint256 shares);
event CancelDepositRequest(address indexed sender);
Expand All @@ -90,7 +81,7 @@ contract LiquidityPool is Auth, IERC4626 {
poolId = poolId_;
trancheId = trancheId_;
asset = asset_;
share = TrancheTokenLike(share_);
share = IERC20Metadata(share_);
escrow = escrow_;
manager = ManagerLike(manager_);

Expand All @@ -105,7 +96,7 @@ contract LiquidityPool is Auth, IERC4626 {
emit File(what, data);
}

// --- ERC4626 functions ---
// --- ERC-4626 methods ---
/// @return Total value of the shares, denominated in the asset of this Liquidity Pool
function totalAssets() public view returns (uint256) {
return convertToAssets(totalSupply());
Expand Down Expand Up @@ -135,14 +126,14 @@ contract LiquidityPool is Auth, IERC4626 {

function deposit(uint256 assets, address receiver) public returns (uint256 shares) {
shares = manager.deposit(address(this), assets, receiver, msg.sender);
emit Deposit(address(this), receiver, assets, shares);
emit Deposit(msg.sender, receiver, assets, shares);
}

/// @notice Collect shares for deposited assets after Centrifuge epoch execution.
/// maxMint is the max amount of shares that can be minted.
function mint(uint256 shares, address receiver) public returns (uint256 assets) {
assets = manager.mint(address(this), shares, receiver, msg.sender);
emit Deposit(address(this), receiver, assets, shares);
emit Deposit(msg.sender, receiver, assets, shares);
}

/// @notice maxShares that can be claimed by the receiver after the epoch has been executed on the Centrifuge side.
Expand All @@ -162,7 +153,7 @@ contract LiquidityPool is Auth, IERC4626 {
function withdraw(uint256 assets, address receiver, address owner) public returns (uint256 shares) {
require((msg.sender == owner), "LiquidityPool/not-the-owner");
shares = manager.withdraw(address(this), assets, receiver, owner);
emit Withdraw(address(this), receiver, owner, assets, shares);
emit Withdraw(msg.sender, receiver, owner, assets, shares);
}

/// @notice maxShares that can be redeemed by the owner after redemption was requested
Expand All @@ -178,10 +169,10 @@ contract LiquidityPool is Auth, IERC4626 {
function redeem(uint256 shares, address receiver, address owner) public returns (uint256 assets) {
require((msg.sender == owner), "LiquidityPool/not-the-owner");
assets = manager.redeem(address(this), shares, receiver, owner);
emit Withdraw(address(this), receiver, owner, assets, shares);
emit Withdraw(msg.sender, receiver, owner, assets, shares);
}

// --- Asynchronous 4626 functions ---
// --- ERC-7540 methods ---
/// @notice Request asset deposit for a receiver to be included in the next epoch execution.
/// @notice Request can only be called by the owner of the assets
/// Asset is locked in the escrow on request submission
Expand All @@ -201,7 +192,7 @@ contract LiquidityPool is Auth, IERC4626 {
_withPermit(asset, owner, address(this), assets, deadline, v, r, s);
require(manager.requestDeposit(address(this), assets, owner, owner), "LiquidityPool/request-deposit-failed");
SafeTransferLib.safeTransferFrom(asset, owner, address(escrow), assets);
emit DepositRequest(owner, owner, assets);
emit DepositRequest(msg.sender, owner, assets);
}

/// @notice View the total amount the operator has requested to deposit but isn't able to deposit or mint yet
Expand All @@ -211,20 +202,6 @@ contract LiquidityPool is Auth, IERC4626 {
assets = manager.pendingDepositRequest(address(this), operator);
}

/// @notice Request decreasing the outstanding deposit orders. Will return the assets once the order
/// on Centrifuge is successfully decreased.
function decreaseDepositRequest(uint256 assets) public {
manager.decreaseDepositRequest(address(this), assets, msg.sender);
emit DecreaseDepositRequest(msg.sender, assets);
}

/// @notice Request cancelling the outstanding deposit orders. Will return the assets once the order
/// on Centrifuge is successfully cancelled.
function cancelDepositRequest() public {
manager.cancelDepositRequest(address(this), msg.sender);
emit CancelDepositRequest(msg.sender);
}

/// @notice Request share redemption for a receiver to be included in the next epoch execution.
/// DOES support flow where owner != msg.sender but has allowance to spend its shares
/// Shares are locked in the escrow on request submission
Expand All @@ -239,20 +216,6 @@ contract LiquidityPool is Auth, IERC4626 {
emit RedeemRequest(msg.sender, operator, owner, shares);
}

/// @notice Request decreasing the outstanding redemption orders. Will return the shares once the order
/// on Centrifuge is successfully decreased.
function decreaseRedeemRequest(uint256 shares) public {
manager.decreaseRedeemRequest(address(this), shares, msg.sender);
emit DecreaseRedeemRequest(msg.sender, shares);
}

/// @notice Request cancelling the outstanding redemption orders. Will return the shares once the order
/// on Centrifuge is successfully cancelled.
function cancelRedeemRequest() public {
manager.cancelRedeemRequest(address(this), msg.sender);
emit CancelRedeemRequest(msg.sender);
}

/// @notice View the total amount the operator has requested to redeem but isn't able to withdraw or redeem yet
/// @dev Due to the asynchronous nature, this value might be outdated, and should only
/// be used for informational purposes.
Expand All @@ -277,7 +240,36 @@ contract LiquidityPool is Auth, IERC4626 {
revert();
}

// --- ERC20 overrides ---
// --- Misc asynchronous vault methods ---
/// @notice Request decreasing the outstanding deposit orders. Will return the assets once the order
/// on Centrifuge is successfully decreased.
function decreaseDepositRequest(uint256 assets) public {
manager.decreaseDepositRequest(address(this), assets, msg.sender);
emit DecreaseDepositRequest(msg.sender, assets);
}

/// @notice Request cancelling the outstanding deposit orders. Will return the assets once the order
/// on Centrifuge is successfully cancelled.
function cancelDepositRequest() public {
manager.cancelDepositRequest(address(this), msg.sender);
emit CancelDepositRequest(msg.sender);
}

/// @notice Request decreasing the outstanding redemption orders. Will return the shares once the order
/// on Centrifuge is successfully decreased.
function decreaseRedeemRequest(uint256 shares) public {
manager.decreaseRedeemRequest(address(this), shares, msg.sender);
emit DecreaseRedeemRequest(msg.sender, shares);
}

/// @notice Request cancelling the outstanding redemption orders. Will return the shares once the order
/// on Centrifuge is successfully cancelled.
function cancelRedeemRequest() public {
manager.cancelRedeemRequest(address(this), msg.sender);
emit CancelRedeemRequest(msg.sender);
}

// --- ERC-20 overrides ---
function name() public view returns (string memory) {
return share.name();
}
Expand Down Expand Up @@ -342,7 +334,7 @@ contract LiquidityPool is Auth, IERC4626 {
bytes32 r,
bytes32 s
) internal {
try ERC20PermitLike(token).permit(owner, spender, value, deadline, v, r, s) {
try IERC20Permit(token).permit(owner, spender, value, deadline, v, r, s) {
return;
} catch {
if (IERC20(token).allowance(owner, spender) == value) {
Expand Down
Loading

0 comments on commit f1b721c

Please sign in to comment.