Skip to content

Commit

Permalink
chore: merge sc-482 branch
Browse files Browse the repository at this point in the history
  • Loading branch information
lucas-manuel committed Jul 4, 2024
2 parents 7b6cd3b + b3db93e commit 281637b
Show file tree
Hide file tree
Showing 10 changed files with 1,451 additions and 559 deletions.
86 changes: 43 additions & 43 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,51 +37,51 @@ jobs:
BASE_RPC_URL: ${{secrets.BASE_RPC_URL}}
run: FOUNDRY_PROFILE=pr forge test -vv --show-progress

# coverage:
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v3
coverage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

# - name: Install Foundry
# uses: foundry-rs/foundry-toolchain@v1
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1

# - name: Run coverage
# env:
# MAINNET_RPC_URL: ${{secrets.MAINNET_RPC_URL}}
# OPTIMISM_RPC_URL: ${{secrets.OPTIMISM_RPC_URL}}
# ARBITRUM_ONE_RPC_URL: ${{secrets.ARBITRUM_ONE_RPC_URL}}
# ARBITRUM_NOVA_RPC_URL: ${{secrets.ARBITRUM_NOVA_RPC_URL}}
# GNOSIS_CHAIN_RPC_URL: ${{secrets.GNOSIS_CHAIN_RPC_URL}}
# BASE_RPC_URL: ${{secrets.BASE_RPC_URL}}
# run: forge coverage --report summary --report lcov
- name: Run coverage
env:
MAINNET_RPC_URL: ${{secrets.MAINNET_RPC_URL}}
OPTIMISM_RPC_URL: ${{secrets.OPTIMISM_RPC_URL}}
ARBITRUM_ONE_RPC_URL: ${{secrets.ARBITRUM_ONE_RPC_URL}}
ARBITRUM_NOVA_RPC_URL: ${{secrets.ARBITRUM_NOVA_RPC_URL}}
GNOSIS_CHAIN_RPC_URL: ${{secrets.GNOSIS_CHAIN_RPC_URL}}
BASE_RPC_URL: ${{secrets.BASE_RPC_URL}}
run: forge coverage --report summary --report lcov

# # To ignore coverage for certain directories modify the paths in this step as needed. The
# # below default ignores coverage results for the test and script directories. Alternatively,
# # to include coverage in all directories, comment out this step. Note that because this
# # filtering applies to the lcov file, the summary table generated in the previous step will
# # still include all files and directories.
# # The `--rc lcov_branch_coverage=1` part keeps branch info in the filtered report, since lcov
# # defaults to removing branch info.
# - name: Filter directories
# run: |
# sudo apt update && sudo apt install -y lcov
# lcov --remove lcov.info 'test/*' 'script/*' --output-file lcov.info --rc lcov_branch_coverage=1
# To ignore coverage for certain directories modify the paths in this step as needed. The
# below default ignores coverage results for the test and script directories. Alternatively,
# to include coverage in all directories, comment out this step. Note that because this
# filtering applies to the lcov file, the summary table generated in the previous step will
# still include all files and directories.
# The `--rc lcov_branch_coverage=1` part keeps branch info in the filtered report, since lcov
# defaults to removing branch info.
- name: Filter directories
run: |
sudo apt update && sudo apt install -y lcov
lcov --remove lcov.info 'test/*' 'script/*' --output-file lcov.info --rc lcov_branch_coverage=1
# # This step posts a detailed coverage report as a comment and deletes previous comments on
# # each push. The below step is used to fail coverage if the specified coverage threshold is
# # not met. The below step can post a comment (when it's `github-token` is specified) but it's
# # not as useful, and this action cannot fail CI based on a minimum coverage threshold, which
# # is why we use both in this way.
# - name: Post coverage report
# if: github.event_name == 'pull_request' # This action fails when ran outside of a pull request.
# uses: romeovs/[email protected]
# with:
# delete-old-comments: true
# lcov-file: ./lcov.info
# github-token: ${{ secrets.GITHUB_TOKEN }} # Adds a coverage summary comment to the PR.
# This step posts a detailed coverage report as a comment and deletes previous comments on
# each push. The below step is used to fail coverage if the specified coverage threshold is
# not met. The below step can post a comment (when it's `github-token` is specified) but it's
# not as useful, and this action cannot fail CI based on a minimum coverage threshold, which
# is why we use both in this way.
- name: Post coverage report
if: github.event_name == 'pull_request' # This action fails when ran outside of a pull request.
uses: romeovs/[email protected]
with:
delete-old-comments: true
lcov-file: ./lcov.info
github-token: ${{ secrets.GITHUB_TOKEN }} # Adds a coverage summary comment to the PR.

# - name: Verify minimum coverage
# uses: zgosalvez/github-actions-report-lcov@v2
# with:
# coverage-files: ./lcov.info
# minimum-coverage: 90 # Set coverage threshold.
- name: Verify minimum coverage
uses: zgosalvez/github-actions-report-lcov@v2
with:
coverage-files: ./lcov.info
minimum-coverage: 90 # Set coverage threshold.
160 changes: 106 additions & 54 deletions src/PSM3.sol
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ contract PSM3 is IPSM3 {
/*** Swap functions ***/
/**********************************************************************************************/

function swap(
function swapExactIn(
address assetIn,
address assetOut,
uint256 amountIn,
Expand All @@ -66,7 +66,7 @@ contract PSM3 is IPSM3 {
require(amountIn != 0, "PSM3/invalid-amountIn");
require(receiver != address(0), "PSM3/invalid-receiver");

amountOut = previewSwap(assetIn, assetOut, amountIn);
amountOut = previewSwapExactIn(assetIn, assetOut, amountIn);

require(amountOut >= minAmountOut, "PSM3/amountOut-too-low");

Expand All @@ -76,6 +76,29 @@ contract PSM3 is IPSM3 {
emit Swap(assetIn, assetOut, msg.sender, receiver, amountIn, amountOut, referralCode);
}

function swapExactOut(
address assetIn,
address assetOut,
uint256 amountOut,
uint256 maxAmountIn,
address receiver,
uint256 referralCode
)
external override returns (uint256 amountIn)
{
require(amountOut != 0, "PSM3/invalid-amountOut");
require(receiver != address(0), "PSM3/invalid-receiver");

amountIn = previewSwapExactOut(assetIn, assetOut, amountOut);

require(amountIn <= maxAmountIn, "PSM3/amountIn-too-high");

IERC20(assetIn).safeTransferFrom(msg.sender, address(this), amountIn);
IERC20(assetOut).safeTransfer(receiver, amountOut);

emit Swap(assetIn, assetOut, msg.sender, receiver, amountIn, amountOut, referralCode);
}

/**********************************************************************************************/
/*** Liquidity provision functions ***/
/**********************************************************************************************/
Expand Down Expand Up @@ -154,25 +177,18 @@ contract PSM3 is IPSM3 {
/*** Swap preview functions ***/
/**********************************************************************************************/

function previewSwap(address assetIn, address assetOut, uint256 amountIn)
function previewSwapExactIn(address assetIn, address assetOut, uint256 amountIn)
public view override returns (uint256 amountOut)
{
if (assetIn == address(asset0)) {
if (assetOut == address(asset1)) return _previewOneToOneSwap(amountIn, _asset0Precision, _asset1Precision);
else if (assetOut == address(asset2)) return _previewSwapToAsset2(amountIn, _asset0Precision);
}

else if (assetIn == address(asset1)) {
if (assetOut == address(asset0)) return _previewOneToOneSwap(amountIn, _asset1Precision, _asset0Precision);
else if (assetOut == address(asset2)) return _previewSwapToAsset2(amountIn, _asset1Precision);
}

else if (assetIn == address(asset2)) {
if (assetOut == address(asset0)) return _previewSwapFromAsset2(amountIn, _asset0Precision);
else if (assetOut == address(asset1)) return _previewSwapFromAsset2(amountIn, _asset1Precision);
}
// Round down to get amountOut
amountOut = _getSwapQuote(assetIn, assetOut, amountIn, false);
}

revert("PSM3/invalid-asset");
function previewSwapExactOut(address assetIn, address assetOut, uint256 amountOut)
public view override returns (uint256 amountIn)
{
// Round up to get amountIn
amountIn = _getSwapQuote(assetOut, assetIn, amountOut, true);
}

/**********************************************************************************************/
Expand Down Expand Up @@ -229,23 +245,9 @@ contract PSM3 is IPSM3 {
}

/**********************************************************************************************/
/*** Internal helper functions ***/
/*** Internal valuation functions (deposit/withdraw) ***/
/**********************************************************************************************/

function _convertToSharesRoundUp(uint256 assetValue) internal view returns (uint256) {
uint256 totalValue = getPsmTotalValue();
if (totalValue != 0) {
return _divUp(assetValue * totalShares, totalValue);
}
return assetValue;
}

function _divUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
unchecked {
z = x != 0 ? ((x - 1) / y) + 1 : 0;
}
}

function _getAssetValue(address asset, uint256 amount) internal view returns (uint256) {
if (asset == address(asset0)) return _getAsset0Value(amount);
else if (asset == address(asset1)) return _getAsset1Value(amount);
Expand All @@ -269,40 +271,90 @@ contract PSM3 is IPSM3 {
/ _asset2Precision;
}

function _isValidAsset(address asset) internal view returns (bool) {
return asset == address(asset0) || asset == address(asset1) || asset == address(asset2);
/**********************************************************************************************/
/*** Internal preview functions (swaps) ***/
/**********************************************************************************************/

function _getSwapQuote(address asset, address quoteAsset, uint256 amount, bool roundUp)
public view returns (uint256 quoteAmount)
{
if (asset == address(asset0)) {
if (quoteAsset == address(asset1)) return _convertOneToOne(amount, _asset0Precision, _asset1Precision, roundUp);
else if (quoteAsset == address(asset2)) return _convertToAsset2(amount, _asset0Precision, roundUp);
}

else if (asset == address(asset1)) {
if (quoteAsset == address(asset0)) return _convertOneToOne(amount, _asset1Precision, _asset0Precision, roundUp);
else if (quoteAsset == address(asset2)) return _convertToAsset2(amount, _asset1Precision, roundUp);
}

else if (asset == address(asset2)) {
if (quoteAsset == address(asset0)) return _convertFromAsset2(amount, _asset0Precision, roundUp);
else if (quoteAsset == address(asset1)) return _convertFromAsset2(amount, _asset1Precision, roundUp);
}

revert("PSM3/invalid-asset");
}

function _previewSwapToAsset2(uint256 amountIn, uint256 assetInPrecision)
function _convertToAsset2(uint256 amount, uint256 assetPrecision, bool roundUp)
internal view returns (uint256)
{
return amountIn
* 1e27
/ IRateProviderLike(rateProvider).getConversionRate()
* _asset2Precision
/ assetInPrecision;
uint256 rate = IRateProviderLike(rateProvider).getConversionRate();

if (!roundUp) return amount * 1e27 / rate * _asset2Precision / assetPrecision;

return _divUp(
_divUp(amount * 1e27, rate) * _asset2Precision,
assetPrecision
);
}

function _previewSwapFromAsset2(uint256 amountIn, uint256 assetInPrecision)
function _convertFromAsset2(uint256 amount, uint256 assetPrecision, bool roundUp)
internal view returns (uint256)
{
return amountIn
* IRateProviderLike(rateProvider).getConversionRate()
/ 1e27
* assetInPrecision
/ _asset2Precision;
uint256 rate = IRateProviderLike(rateProvider).getConversionRate();

if (!roundUp) return amount * rate / 1e27 * assetPrecision / _asset2Precision;

return _divUp(
_divUp(amount * rate, 1e27) * assetPrecision,
_asset2Precision
);
}

function _previewOneToOneSwap(
uint256 amountIn,
uint256 assetInPrecision,
uint256 assetOutPrecision
function _convertOneToOne(
uint256 amount,
uint256 assetPrecision,
uint256 convertAssetPrecision,
bool roundUp
)
internal pure returns (uint256)
{
return amountIn
* assetOutPrecision
/ assetInPrecision;
if (!roundUp) return amount * convertAssetPrecision / assetPrecision;

return _divUp(amount * convertAssetPrecision, assetPrecision);
}

/**********************************************************************************************/
/*** Internal helper functions ***/
/**********************************************************************************************/

function _convertToSharesRoundUp(uint256 assetValue) internal view returns (uint256) {
uint256 totalValue = getPsmTotalValue();
if (totalValue != 0) {
return _divUp(assetValue * totalShares, totalValue);
}
return assetValue;
}

function _divUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
unchecked {
z = x != 0 ? ((x - 1) / y) + 1 : 0;
}
}

function _isValidAsset(address asset) internal view returns (bool) {
return asset == address(asset0) || asset == address(asset1) || asset == address(asset2);
}

}
60 changes: 47 additions & 13 deletions src/interfaces/IPSM3.sol
Original file line number Diff line number Diff line change
Expand Up @@ -113,19 +113,19 @@ interface IPSM3 {
/**********************************************************************************************/

/**
* @dev Swaps an amount of assetIn for assetOut in the PSM. The amount swapped is converted
* based on the current value of the two assets used in the swap. This function will
* revert if there is not enough balance in the PSM to facilitate the swap. Both assets
* must be supported in the PSM in order to succeed.
* @param assetIn Address of the ERC-20 asset to swap in.
* @param assetOut Address of the ERC-20 asset to swap out.
* @param amountIn Amount of the asset to swap in.
* @param minAmountOut Minimum amount of the asset to receive.
* @param receiver Address of the receiver of the swapped assets.
* @param referralCode Referral code for the swap.
* @return amountOut Amount of the asset that will be received in the swap.
* @dev Swaps a specified amount of assetIn for assetOut in the PSM. The amount swapped is
* converted based on the current value of the two assets used in the swap. This
* function will revert if there is not enough balance in the PSM to facilitate the
* swap. Both assets must be supported in the PSM in order to succeed.
* @param assetIn Address of the ERC-20 asset to swap in.
* @param assetOut Address of the ERC-20 asset to swap out.
* @param amountIn Amount of the asset to swap in.
* @param minAmountOut Minimum amount of the asset to receive.
* @param receiver Address of the receiver of the swapped assets.
* @param referralCode Referral code for the swap.
* @return amountOut Resulting mount of the asset that will be received in the swap.
*/
function swap(
function swapExactIn(
address assetIn,
address assetOut,
uint256 amountIn,
Expand All @@ -134,6 +134,28 @@ interface IPSM3 {
uint256 referralCode
) external returns (uint256 amountOut);

/**
* @dev Swaps a derived amount of assetIn for a specific amount of assetOut in the PSM. The
* amount swapped is converted based on the current value of the two assets used in
* the swap. This function will revert if there is not enough balance in the PSM to
* facilitate the swap. Both assets must be supported in the PSM in order to succeed.
* @param assetIn Address of the ERC-20 asset to swap in.
* @param assetOut Address of the ERC-20 asset to swap out.
* @param amountOut Amount of the asset to receive from the swap.
* @param maxAmountIn Max amount of the asset to use for the swap.
* @param receiver Address of the receiver of the swapped assets.
* @param referralCode Referral code for the swap.
* @return amountIn Resulting amount of the asset swapped in.
*/
function swapExactOut(
address assetIn,
address assetOut,
uint256 amountOut,
uint256 maxAmountIn,
address receiver,
uint256 referralCode
) external returns (uint256 amountIn);

/**********************************************************************************************/
/*** Liquidity provision functions ***/
/**********************************************************************************************/
Expand Down Expand Up @@ -206,9 +228,21 @@ interface IPSM3 {
* @param amountIn Amount of the asset to swap in.
* @return amountOut Amount of the asset that will be received in the swap.
*/
function previewSwap(address assetIn, address assetOut, uint256 amountIn)
function previewSwapExactIn(address assetIn, address assetOut, uint256 amountIn)
external view returns (uint256 amountOut);

/**
* @dev View function that returns the exact amount of assetIn that would be required to
* receive a given amount of assetOut in a swap. The amount returned is
* converted based on the current value of the two assets used in the swap.
* @param assetIn Address of the ERC-20 asset to swap in.
* @param assetOut Address of the ERC-20 asset to swap out.
* @param amountOut Amount of the asset to receive from the swap.
* @return amountIn Amount of the asset that is required to receive amountOut.
*/
function previewSwapExactOut(address assetIn, address assetOut, uint256 amountOut)
external view returns (uint256 amountIn);

/**********************************************************************************************/
/*** Conversion functions ***/
/**********************************************************************************************/
Expand Down
Loading

0 comments on commit 281637b

Please sign in to comment.