Skip to content

Commit

Permalink
Merge pull request #330 from tranchess/dev-bc-flashswap-v3
Browse files Browse the repository at this point in the history
Add flashswap v3
  • Loading branch information
tpawn authored Feb 2, 2024
2 parents 8d3ee5f + 6802af6 commit 7cd566a
Show file tree
Hide file tree
Showing 6 changed files with 762 additions and 4 deletions.
86 changes: 86 additions & 0 deletions contracts/fund/PrimaryMarketV5.sol
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,30 @@ contract PrimaryMarketV5 is IPrimaryMarketV5, ReentrancyGuard, ITrancheIndexV2,
}
}

/// @notice Calculate the amount of underlying tokens to create at least the given QUEEN amount.
/// This only works with non-empty fund for simplicity.
/// @param minOutQ Minimum received QUEEN amount
/// @return underlying Underlying amount that should be used for creation
function getCreationForQ(uint256 minOutQ) external view override returns (uint256 underlying) {
// Assume:
// minOutQ * fundUnderlying = a * fundEquivalentTotalQ - b
// where a and b are integers and 0 <= b < fundEquivalentTotalQ
// Then
// underlying = a
// getCreation(underlying)
// = floor(a * fundEquivalentTotalQ / fundUnderlying)
// >= floor((a * fundEquivalentTotalQ - b) / fundUnderlying)
// = minOutQ
// getCreation(underlying - 1)
// = floor((a * fundEquivalentTotalQ - fundEquivalentTotalQ) / fundUnderlying)
// < (a * fundEquivalentTotalQ - b) / fundUnderlying
// = minOutQ
uint256 fundUnderlying = IFundV3(fund).getTotalUnderlying();
uint256 fundEquivalentTotalQ = IFundV3(fund).getEquivalentTotalQ();
require(fundEquivalentTotalQ > 0, "Cannot calculate creation for empty fund");
return minOutQ.mul(fundUnderlying).add(fundEquivalentTotalQ - 1).div(fundEquivalentTotalQ);
}

function _getRedemption(uint256 inQ) private view returns (uint256 underlying) {
uint256 fundUnderlying = IFundV3(fund).getTotalUnderlying();
uint256 fundEquivalentTotalQ = IFundV3(fund).getEquivalentTotalQ();
Expand All @@ -139,6 +163,39 @@ contract PrimaryMarketV5 is IPrimaryMarketV5, ReentrancyGuard, ITrancheIndexV2,
underlying = _getRedemption(inQ - feeQ);
}

/// @notice Calculate the amount of QUEEN that can be redeemed for at least the given amount
/// of underlying tokens.
/// @dev The return value may not be the minimum solution due to rounding errors.
/// @param minUnderlying Minimum received underlying amount
/// @return inQ QUEEN amount that should be redeemed
function getRedemptionForUnderlying(
uint256 minUnderlying
) external view override returns (uint256 inQ) {
// Assume:
// minUnderlying * fundEquivalentTotalQ = a * fundUnderlying - b
// a * 1e18 = c * (1e18 - redemptionFeeRate) + d
// where
// a, b, c, d are integers
// 0 <= b < fundUnderlying
// 0 <= d < 1e18 - redemeptionFeeRate
// Then
// inQAfterFee = a
// inQ = c
// getRedemption(inQ).underlying
// = floor((c - floor(c * redemptionFeeRate / 1e18)) * fundUnderlying / fundEquivalentTotalQ)
// = floor(ceil(c * (1e18 - redemptionFeeRate) / 1e18) * fundUnderlying / fundEquivalentTotalQ)
// = floor(((c * (1e18 - redemptionFeeRate) + d) / 1e18) * fundUnderlying / fundEquivalentTotalQ)
// = floor(a * fundUnderlying / fundEquivalentTotalQ)
// => floor((a * fundUnderlying - b) / fundEquivalentTotalQ)
// = minUnderlying
uint256 fundUnderlying = IFundV3(fund).getTotalUnderlying();
uint256 fundEquivalentTotalQ = IFundV3(fund).getEquivalentTotalQ();
uint256 inQAfterFee = minUnderlying.mul(fundEquivalentTotalQ).add(fundUnderlying - 1).div(
fundUnderlying
);
return inQAfterFee.divideDecimal(1e18 - redemptionFeeRate);
}

/// @notice Calculate the result of a split.
/// @param inQ QUEEN amount to be split
/// @return outB Received BISHOP amount
Expand All @@ -148,6 +205,19 @@ contract PrimaryMarketV5 is IPrimaryMarketV5, ReentrancyGuard, ITrancheIndexV2,
outB = outR.mul(_weightB);
}

/// @notice Calculate the amount of QUEEN that can be split into at least the given amount of
/// BISHOP and ROOK.
/// @param minOutR Received ROOK amount
/// @return inQ QUEEN amount that should be split
/// @return outB Received BISHOP amount
function getSplitForR(
uint256 minOutR
) external view override returns (uint256 inQ, uint256 outB) {
uint256 splitRatio = IFundV3(fund).splitRatio();
outB = minOutR.mul(_weightB);
inQ = minOutR.mul(1e18).add(splitRatio.sub(1)).div(splitRatio);
}

/// @notice Calculate the result of a merge.
/// @param inB Spent BISHOP amount
/// @return inR Spent ROOK amount
Expand All @@ -167,6 +237,22 @@ contract PrimaryMarketV5 is IPrimaryMarketV5, ReentrancyGuard, ITrancheIndexV2,
}
}

/// @notice Calculate the result of a merge using ROOK.
/// @param inR Spent ROOK amount
/// @return inB Spent BISHOP amount
/// @return outQ Received QUEEN amount
/// @return feeQ QUEEN amount charged as merge fee
function getMergeByR(
uint256 inR
) public view override returns (uint256 inB, uint256 outQ, uint256 feeQ) {
require(!IFundV5(fund).frozen(), "Fund frozen");
inB = inR.mul(_weightB);
uint256 splitRatio = IFundV5(fund).splitRatio();
uint256 outQBeforeFee = inR.divideDecimal(splitRatio);
feeQ = outQBeforeFee.multiplyDecimal(mergeFeeRate);
outQ = outQBeforeFee.sub(feeQ);
}

/// @notice Return index of the first queued redemption that cannot be claimed now.
/// Users can use this function to determine which indices can be passed to
/// `claimRedemptions()`.
Expand Down
48 changes: 47 additions & 1 deletion contracts/fund/WstETHPrimaryMarketRouter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ import "../interfaces/IPrimaryMarketV5.sol";
import "../interfaces/IFundV3.sol";
import "../interfaces/IWstETH.sol";
import "../interfaces/ITrancheIndexV2.sol";
import "../interfaces/IStableSwap.sol";

contract WstETHPrimaryMarketRouter is ITrancheIndexV2 {
contract WstETHPrimaryMarketRouter is IStableSwapCore, ITrancheIndexV2 {
using SafeERC20 for IERC20;

IPrimaryMarketV5 public immutable primaryMarket;
Expand All @@ -28,6 +29,51 @@ contract WstETHPrimaryMarketRouter is ITrancheIndexV2 {
_tokenB = fund_.tokenB();
}

/// @dev Get redemption with StableSwap getQuoteOut interface.
function getQuoteOut(uint256 baseIn) external view override returns (uint256 quoteOut) {
(quoteOut, ) = primaryMarket.getRedemption(baseIn);
}

/// @dev Get creation for QUEEN with StableSwap getQuoteIn interface.
function getQuoteIn(uint256 baseOut) external view override returns (uint256 quoteIn) {
quoteIn = primaryMarket.getCreationForQ(baseOut);
}

/// @dev Get creation with StableSwap getBaseOut interface.
function getBaseOut(uint256 quoteIn) external view override returns (uint256 baseOut) {
baseOut = primaryMarket.getCreation(quoteIn);
}

/// @dev Get redemption for underlying with StableSwap getBaseIn interface.
function getBaseIn(uint256 quoteOut) external view override returns (uint256 baseIn) {
baseIn = primaryMarket.getRedemptionForUnderlying(quoteOut);
}

/// @dev Create QUEEN with StableSwap buy interface.
/// Underlying should have already been sent to this contract
function buy(
uint256 version,
uint256 baseOut,
address recipient,
bytes calldata
) external override returns (uint256 realBaseOut) {
uint256 routerQuoteBalance = IERC20(_wstETH).balanceOf(address(this));
IERC20(_wstETH).safeTransfer(address(primaryMarket), routerQuoteBalance);
realBaseOut = primaryMarket.create(recipient, baseOut, version);
}

/// @dev Redeem QUEEN with StableSwap sell interface.
/// QUEEN should have already been sent to this contract
function sell(
uint256 version,
uint256 quoteOut,
address recipient,
bytes calldata
) external override returns (uint256 realQuoteOut) {
uint256 routerBaseBalance = fund.trancheBalanceOf(TRANCHE_Q, address(this));
realQuoteOut = primaryMarket.redeem(recipient, routerBaseBalance, quoteOut, version);
}

function create(
address recipient,
bool needWrap,
Expand Down
12 changes: 11 additions & 1 deletion contracts/interfaces/IPrimaryMarketV5.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,22 @@ interface IPrimaryMarketV5 {

function getCreation(uint256 underlying) external view returns (uint256 outQ);

function getCreationForQ(uint256 minOutQ) external view returns (uint256 underlying);

function getRedemption(uint256 inQ) external view returns (uint256 underlying, uint256 fee);

function getSplit(uint256 inQ) external view returns (uint256 outB, uint256 outQ);
function getRedemptionForUnderlying(uint256 minUnderlying) external view returns (uint256 inQ);

function getSplit(uint256 inQ) external view returns (uint256 outB, uint256 outR);

function getSplitForR(uint256 minOutR) external view returns (uint256 inQ, uint256 outB);

function getMerge(uint256 inB) external view returns (uint256 inR, uint256 outQ, uint256 feeQ);

function getMergeByR(
uint256 inR
) external view returns (uint256 inB, uint256 outQ, uint256 feeQ);

function canBeRemovedFromFund() external view returns (bool);

function create(
Expand Down
Loading

0 comments on commit 7cd566a

Please sign in to comment.