-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add preview deposit withdraw tests (SC-499) (#26)
* feat: add first tests * fix: rm duplicate file * feat: update preview deposit coverage * feat: start on withdraw tests * feat: unit tests complete * feat: add fuzz test * fix: update file names
- Loading branch information
1 parent
a0c1153
commit b02926d
Showing
4 changed files
with
315 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,4 +26,5 @@ contract RateSetterHandler is StdUtils { | |
|
||
setRateCount++; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.