Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add preview deposit withdraw tests (SC-499) #26

Merged
merged 9 commits into from
Jul 29, 2024
1 change: 1 addition & 0 deletions test/invariant/handlers/RateSetterHandler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,5 @@ contract RateSetterHandler is StdUtils {

setRateCount++;
}

}
131 changes: 131 additions & 0 deletions test/unit/PreviewDeposit.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.13;

import "forge-std/Test.sol";

import { MockRateProvider, PSMTestBase } from "test/PSMTestBase.sol";

contract PSMPreviewDeposit_FailureTests is PSMTestBase {

function test_previewDeposit_invalidAsset() public {
vm.expectRevert("PSM3/invalid-asset");
psm.previewDeposit(makeAddr("other-token"), 1);
}

}

contract PSMPreviewDeposit_SuccessTests is PSMTestBase {

address depositor = makeAddr("depositor");

function test_previewDeposit_dai_firstDeposit() public view {
assertEq(psm.previewDeposit(address(dai), 1), 1);
assertEq(psm.previewDeposit(address(dai), 2), 2);
assertEq(psm.previewDeposit(address(dai), 3), 3);

assertEq(psm.previewDeposit(address(dai), 1e18), 1e18);
assertEq(psm.previewDeposit(address(dai), 2e18), 2e18);
assertEq(psm.previewDeposit(address(dai), 3e18), 3e18);
}

function testFuzz_previewDeposit_dai_firstDeposit(uint256 amount) public view {
amount = _bound(amount, 0, DAI_TOKEN_MAX);
assertEq(psm.previewDeposit(address(dai), amount), amount);
}

function test_previewDeposit_usdc_firstDeposit() public view {
assertEq(psm.previewDeposit(address(usdc), 1), 1e12);
assertEq(psm.previewDeposit(address(usdc), 2), 2e12);
assertEq(psm.previewDeposit(address(usdc), 3), 3e12);

assertEq(psm.previewDeposit(address(usdc), 1e6), 1e18);
assertEq(psm.previewDeposit(address(usdc), 2e6), 2e18);
assertEq(psm.previewDeposit(address(usdc), 3e6), 3e18);
}

function testFuzz_previewDeposit_usdc_firstDeposit(uint256 amount) public view {
amount = _bound(amount, 0, USDC_TOKEN_MAX);
assertEq(psm.previewDeposit(address(usdc), amount), amount * 1e12);
}

function test_previewDeposit_sDai_firstDeposit() public view {
assertEq(psm.previewDeposit(address(sDai), 1), 1);
assertEq(psm.previewDeposit(address(sDai), 2), 2);
assertEq(psm.previewDeposit(address(sDai), 3), 3);
assertEq(psm.previewDeposit(address(sDai), 4), 5);

assertEq(psm.previewDeposit(address(sDai), 1e18), 1.25e18);
assertEq(psm.previewDeposit(address(sDai), 2e18), 2.50e18);
assertEq(psm.previewDeposit(address(sDai), 3e18), 3.75e18);
assertEq(psm.previewDeposit(address(sDai), 4e18), 5.00e18);
}

function testFuzz_previewDeposit_sDai_firstDeposit(uint256 amount) public view {
amount = _bound(amount, 0, SDAI_TOKEN_MAX);
assertEq(psm.previewDeposit(address(sDai), amount), amount * 1.25e27 / 1e27);
}

function test_previewDeposit_afterDepositsAndExchangeRateIncrease() public {
_assertOneToOne();

_deposit(address(dai), depositor, 1e18);
_assertOneToOne();

_deposit(address(usdc), depositor, 1e6);
_assertOneToOne();

_deposit(address(sDai), depositor, 0.8e18);
_assertOneToOne();

mockRateProvider.__setConversionRate(2e27);

// $300 dollars of value deposited, 300 shares minted.
// sDAI portion becomes worth $160, full pool worth $360, each share worth $1.20
// 1 USDC = 1/1.20 = 0.833...
assertEq(psm.previewDeposit(address(dai), 1e18), 0.833333333333333333e18);
assertEq(psm.previewDeposit(address(usdc), 1e6), 0.833333333333333333e18);
assertEq(psm.previewDeposit(address(sDai), 1e18), 1.666666666666666666e18); // 1 sDAI = $2
}

function testFuzz_previewDeposit_afterDepositsAndExchangeRateIncrease(
uint256 amount1,
uint256 amount2,
uint256 amount3,
uint256 conversionRate,
uint256 previewAmount
) public {
amount1 = _bound(amount1, 1, DAI_TOKEN_MAX);
amount2 = _bound(amount2, 1, USDC_TOKEN_MAX);
amount3 = _bound(amount3, 1, SDAI_TOKEN_MAX);
conversionRate = _bound(conversionRate, 1.00e27, 1000e27);
previewAmount = _bound(previewAmount, 0, DAI_TOKEN_MAX);

_assertOneToOne();

_deposit(address(dai), depositor, amount1);
_assertOneToOne();

_deposit(address(usdc), depositor, amount2);
_assertOneToOne();

_deposit(address(sDai), depositor, amount3);
_assertOneToOne();

mockRateProvider.__setConversionRate(conversionRate);

uint256 totalSharesMinted = amount1 + amount2 * 1e12 + amount3 * 1.25e27 / 1e27;
uint256 totalValue = amount1 + amount2 * 1e12 + amount3 * conversionRate / 1e27;
uint256 usdcPreviewAmount = previewAmount / 1e12;

assertEq(psm.previewDeposit(address(dai), previewAmount), previewAmount * totalSharesMinted / totalValue);
assertEq(psm.previewDeposit(address(usdc), usdcPreviewAmount), usdcPreviewAmount * 1e12 * totalSharesMinted / totalValue); // Divide then multiply to replicate rounding
assertEq(psm.previewDeposit(address(sDai), previewAmount), (previewAmount * conversionRate / 1e27) * totalSharesMinted / totalValue);
}

function _assertOneToOne() internal view {
assertEq(psm.previewDeposit(address(dai), 1e18), 1e18);
assertEq(psm.previewDeposit(address(usdc), 1e6), 1e18);
assertEq(psm.previewDeposit(address(sDai), 1e18), 1.25e18);
}

}
183 changes: 183 additions & 0 deletions test/unit/PreviewWIthdraw.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.13;

import "forge-std/Test.sol";

import { MockRateProvider, PSMTestBase } from "test/PSMTestBase.sol";

contract PSMPreviewWithdraw_FailureTests is PSMTestBase {

function test_previewWithdraw_invalidAsset() public {
vm.expectRevert("PSM3/invalid-asset");
psm.previewWithdraw(makeAddr("other-token"), 1);
}

}

contract PSMPreviewWithdraw_ZeroAssetsTests is PSMTestBase {

// Always returns zero because there is no balance of assets in the PSM in this case
function test_previewWithdraw_zeroTotalAssets() public {
( uint256 shares1, uint256 assets1 ) = psm.previewWithdraw(address(dai), 1e18);
( uint256 shares2, uint256 assets2 ) = psm.previewWithdraw(address(usdc), 1e6);
( uint256 shares3, uint256 assets3 ) = psm.previewWithdraw(address(sDai), 1e18);

assertEq(shares1, 0);
assertEq(assets1, 0);
assertEq(shares2, 0);
assertEq(assets2, 0);
assertEq(shares3, 0);
assertEq(assets3, 0);

mockRateProvider.__setConversionRate(2e27);

( shares1, assets1 ) = psm.previewWithdraw(address(dai), 1e18);
( shares2, assets2 ) = psm.previewWithdraw(address(usdc), 1e6);
( shares3, assets3 ) = psm.previewWithdraw(address(sDai), 1e18);

assertEq(shares1, 0);
assertEq(assets1, 0);
assertEq(shares2, 0);
assertEq(assets2, 0);
assertEq(shares3, 0);
assertEq(assets3, 0);
}

}

contract PSMPreviewWithdraw_SuccessTests is PSMTestBase {

function setUp() public override {
super.setUp();
// Setup so that address(this) has the most shares, higher underlying balance than PSM
// balance of sDAI and USDC
_deposit(address(dai), address(this), 100e18);
_deposit(address(usdc), makeAddr("usdc-user"), 10e6);
_deposit(address(sDai), makeAddr("sDai-user"), 1e18);
}

function test_previewWithdraw_dai_amountLtUnderlyingBalance() public view {
( uint256 shares, uint256 assets ) = psm.previewWithdraw(address(dai), 100e18 - 1);
assertEq(shares, 100e18 - 1);
assertEq(assets, 100e18 - 1);
}

function test_previewWithdraw_dai_amountEqUnderlyingBalance() public view {
( uint256 shares, uint256 assets ) = psm.previewWithdraw(address(dai), 100e18);
assertEq(shares, 100e18);
assertEq(assets, 100e18);
}

function test_previewWithdraw_dai_amountGtUnderlyingBalance() public view {
( uint256 shares, uint256 assets ) = psm.previewWithdraw(address(dai), 100e18 + 1);
assertEq(shares, 100e18);
assertEq(assets, 100e18);
}

function test_previewWithdraw_usdc_amountLtUnderlyingBalanceAndLtPsmBalance() public view {
( uint256 shares, uint256 assets ) = psm.previewWithdraw(address(usdc), 10e6 - 1);
assertEq(shares, 10e18 - 1e12);
assertEq(assets, 10e6 - 1);
}

function test_previewWithdraw_usdc_amountLtUnderlyingBalanceAndEqPsmBalance() public view {
( uint256 shares, uint256 assets ) = psm.previewWithdraw(address(usdc), 10e6);
assertEq(shares, 10e18);
assertEq(assets, 10e6);
}

function test_previewWithdraw_usdc_amountLtUnderlyingBalanceAndGtPsmBalance() public view {
( uint256 shares, uint256 assets ) = psm.previewWithdraw(address(usdc), 10e6 + 1);
assertEq(shares, 10e18);
assertEq(assets, 10e6);
}

function test_previewWithdraw_sdai_amountLtUnderlyingBalanceAndLtPsmBalance() public view {
( uint256 shares, uint256 assets ) = psm.previewWithdraw(address(sDai), 1e18 - 1);
assertEq(shares, 1.25e18 - 2);
assertEq(assets, 1e18 - 1);
}

function test_previewWithdraw_sdai_amountLtUnderlyingBalanceAndEqPsmBalance() public view {
( uint256 shares, uint256 assets ) = psm.previewWithdraw(address(sDai), 1e18);
assertEq(shares, 1.25e18);
assertEq(assets, 1e18);
}

function test_previewWithdraw_sdai_amountLtUnderlyingBalanceAndGtPsmBalance() public view {
( uint256 shares, uint256 assets ) = psm.previewWithdraw(address(sDai), 1e18 + 1);
assertEq(shares, 1.25e18);
assertEq(assets, 1e18);
}

}

contract PSMPreviewWithdraw_SuccessFuzzTests is PSMTestBase {

struct TestParams {
uint256 amount1;
uint256 amount2;
uint256 amount3;
uint256 previewAmount1;
uint256 previewAmount2;
uint256 previewAmount3;
uint256 conversionRate;
}

function testFuzz_previewWithdraw(TestParams memory params) public {
params.amount1 = _bound(params.amount1, 1, DAI_TOKEN_MAX);
params.amount2 = _bound(params.amount2, 1, USDC_TOKEN_MAX);
params.amount3 = _bound(params.amount3, 1, SDAI_TOKEN_MAX);

// Only covering case of amount being below underlying to focus on value conversion
// and avoid reimplementation of contract logic for dealing with capping amounts
params.previewAmount1 = _bound(params.previewAmount1, 0, params.amount1);
params.previewAmount2 = _bound(params.previewAmount2, 0, params.amount2);
params.previewAmount3 = _bound(params.previewAmount3, 0, params.amount3);

_deposit(address(dai), address(this), params.amount1);
_deposit(address(usdc), address(this), params.amount2);
_deposit(address(sDai), address(this), params.amount3);

( uint256 shares1, uint256 assets1 ) = psm.previewWithdraw(address(dai), params.previewAmount1);
( uint256 shares2, uint256 assets2 ) = psm.previewWithdraw(address(usdc), params.previewAmount2);
( uint256 shares3, uint256 assets3 ) = psm.previewWithdraw(address(sDai), params.previewAmount3);

uint256 totalSharesMinted = params.amount1 + params.amount2 * 1e12 + params.amount3 * 1.25e27 / 1e27;
uint256 totalValue = totalSharesMinted;

assertEq(shares1, params.previewAmount1 * totalSharesMinted / totalValue);
assertEq(shares2, params.previewAmount2 * 1e12 * totalSharesMinted / totalValue);
assertEq(shares3, params.previewAmount3 * 1.25e27 / 1e27 * totalSharesMinted / totalValue);

assertEq(assets1, params.previewAmount1);
assertEq(assets2, params.previewAmount2);
assertEq(assets3, params.previewAmount3);

params.conversionRate = _bound(params.conversionRate, 0.001e27, 1000e27);
mockRateProvider.__setConversionRate(params.conversionRate);

// sDai value accrual changes the value of shares in the PSM
totalValue = params.amount1 + params.amount2 * 1e12 + params.amount3 * params.conversionRate / 1e27;

( shares1, assets1 ) = psm.previewWithdraw(address(dai), params.previewAmount1);
( shares2, assets2 ) = psm.previewWithdraw(address(usdc), params.previewAmount2);
( shares3, assets3 ) = psm.previewWithdraw(address(sDai), params.previewAmount3);

uint256 sDaiConvertedAmount = params.previewAmount3 * params.conversionRate / 1e27;

assertApproxEqAbs(shares1, params.previewAmount1 * totalSharesMinted / totalValue, 1);
assertApproxEqAbs(shares2, params.previewAmount2 * 1e12 * totalSharesMinted / totalValue, 1);
assertApproxEqAbs(shares3, sDaiConvertedAmount * totalSharesMinted / totalValue, 1);

// Assert shares are always rounded up
assertGe(shares1, params.previewAmount1 * totalSharesMinted / totalValue);
assertGe(shares2, params.previewAmount2 * 1e12 * totalSharesMinted / totalValue);
assertGe(shares3, sDaiConvertedAmount * totalSharesMinted / totalValue);

assertApproxEqAbs(assets1, params.previewAmount1, 1);
assertApproxEqAbs(assets2, params.previewAmount2, 1);
assertApproxEqAbs(assets3, params.previewAmount3, 1);
}

}
File renamed without changes.
Loading