-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #125 from gildlab/07-10-2024-convert-erc20priceora…
…clereceiptvault-base-withdraw--tests Add tests for ERC20PriceOracleReceiptVault Base Withdraw
- Loading branch information
Showing
2 changed files
with
368 additions
and
251 deletions.
There are no files selected for viewing
368 changes: 368 additions & 0 deletions
368
test/foundry/src/concrete/erc20PriceOracle/ERC20PriceOracleReceiptVault.baseWithdraw.t.sol
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,368 @@ | ||
// SPDX-License-Identifier: CAL | ||
pragma solidity =0.8.25; | ||
|
||
import {ERC20PriceOracleReceiptVault} from "../../../../../contracts/concrete/vault/ERC20PriceOracleReceiptVault.sol"; | ||
import {ERC20PriceOracleReceiptVaultTest, Vm} from "test/foundry/abstract/ERC20PriceOracleReceiptVaultTest.sol"; | ||
import {TwoPriceOracle} from "../../../../../contracts/oracle/price/TwoPriceOracle.sol"; | ||
import { | ||
LibFixedPointDecimalArithmeticOpenZeppelin, | ||
Math | ||
} from "rain.math.fixedpoint/lib/LibFixedPointDecimalArithmeticOpenZeppelin.sol"; | ||
import {IERC20} from "forge-std/interfaces/IERC20.sol"; | ||
import {Receipt as ReceiptContract} from "../../../../../contracts/concrete/receipt/Receipt.sol"; | ||
import {ZeroAssetsAmount, ZeroReceiver, ZeroOwner} from "../../../../../contracts/abstract/ReceiptVault.sol"; | ||
|
||
contract ERC20PriceOracleReceiptVaultBaseWithdrawTest is ERC20PriceOracleReceiptVaultTest { | ||
using LibFixedPointDecimalArithmeticOpenZeppelin for uint256; | ||
|
||
event WithdrawWithReceipt( | ||
address sender, | ||
address receiver, | ||
address owner, | ||
uint256 assets, | ||
uint256 shares, | ||
uint256 id, | ||
bytes receiptInformation | ||
); | ||
|
||
/// Checks that balance owner balance changes after wirthdraw | ||
function checkBalanceChange( | ||
ERC20PriceOracleReceiptVault vault, | ||
address receiver, | ||
address owner, | ||
uint256 id, | ||
uint256 assets, | ||
ReceiptContract receipt, | ||
bytes memory data | ||
) internal { | ||
uint256 initialBalanceOwner = receipt.balanceOf(owner, id); | ||
uint256 shares = assets.fixedPointMul(id, Math.Rounding.Up); | ||
|
||
vault.setWithdrawId(id); | ||
// Set up the event expectation for WithdrawWithReceipt | ||
vm.expectEmit(true, true, true, true); | ||
emit WithdrawWithReceipt(owner, receiver, owner, assets, shares, id, data); | ||
|
||
// Call withdraw function | ||
vault.withdraw(assets, receiver, owner); | ||
|
||
uint256 balanceAfterOwner = receipt.balanceOf(owner, id); | ||
assertEq(balanceAfterOwner, initialBalanceOwner - shares); | ||
} | ||
|
||
/// Checks that balance owner balance does not change after wirthdraw revert | ||
function checkNoBalanceChange( | ||
ERC20PriceOracleReceiptVault vault, | ||
address receiver, | ||
address owner, | ||
uint256 id, | ||
uint256 assets, | ||
ReceiptContract receipt, | ||
bytes memory expectedRevertData | ||
) internal { | ||
uint256 initialBalanceOwner; | ||
uint256 balanceAfterOwner; | ||
vault.setWithdrawId(id); | ||
|
||
if (owner != address(0)) { | ||
initialBalanceOwner = receipt.balanceOf(owner, id); | ||
} | ||
|
||
// Check if expectedRevertData is provided | ||
if (expectedRevertData.length > 0) { | ||
vm.expectRevert(expectedRevertData); | ||
} else { | ||
vm.expectRevert(); | ||
} | ||
// Call withdraw function | ||
vault.withdraw(assets, receiver, owner); | ||
|
||
if (owner != address(0)) { | ||
balanceAfterOwner = receipt.balanceOf(owner, id); | ||
} | ||
assertEq(balanceAfterOwner, initialBalanceOwner); | ||
} | ||
|
||
/// Test base Withdraw function | ||
function testBaseWithdraw( | ||
uint256 fuzzedKeyAlice, | ||
string memory assetName, | ||
uint256 timestamp, | ||
uint256 assets, | ||
uint8 xauDecimals, | ||
uint8 usdDecimals, | ||
uint80 answeredInRound | ||
) external { | ||
// Ensure the fuzzed key is within the valid range for secp256 | ||
address alice = vm.addr((fuzzedKeyAlice % (SECP256K1_ORDER - 1)) + 1); | ||
// Use common decimal bounds for price feeds | ||
// Use 0-20 so we at least have some coverage higher than 18 | ||
usdDecimals = uint8(bound(usdDecimals, 0, 20)); | ||
xauDecimals = uint8(bound(xauDecimals, 0, 20)); | ||
timestamp = bound(timestamp, 0, type(uint32).max); | ||
|
||
vm.warp(timestamp); | ||
TwoPriceOracle twoPriceOracle = createTwoPriceOracle(usdDecimals, usdDecimals, timestamp, answeredInRound); | ||
vm.startPrank(alice); | ||
// Start recording logs | ||
vm.recordLogs(); | ||
ERC20PriceOracleReceiptVault vault = createVault(address(twoPriceOracle), assetName, assetName); | ||
ReceiptContract receipt = getReceipt(); | ||
|
||
vm.mockCall(address(iAsset), abi.encodeWithSelector(IERC20.totalSupply.selector), abi.encode(1e18)); | ||
// Ensure Alice has enough balance and allowance | ||
vm.mockCall(address(iAsset), abi.encodeWithSelector(IERC20.balanceOf.selector, alice), abi.encode(assets)); | ||
|
||
uint256 totalSupply = iAsset.totalSupply(); | ||
// Getting ZeroSharesAmount if bounded from 1 | ||
assets = bound(assets, 2, totalSupply); | ||
vm.mockCall( | ||
address(iAsset), | ||
abi.encodeWithSelector(IERC20.transferFrom.selector, alice, vault, assets), | ||
abi.encode(true) | ||
); | ||
|
||
uint256 oraclePrice = twoPriceOracle.price(); | ||
|
||
vault.deposit(assets, alice, oraclePrice, bytes("")); | ||
uint256 availableReceiptBalance = receipt.balanceOf(alice, oraclePrice); | ||
checkBalanceChange(vault, alice, alice, oraclePrice, availableReceiptBalance, receipt, bytes("")); | ||
} | ||
|
||
/// Test Withdraw function reverts on zero assets | ||
function testBaseWithdrawRevertsOnZeroAssets( | ||
uint256 fuzzedKeyAlice, | ||
string memory assetName, | ||
uint256 timestamp, | ||
uint256 assets, | ||
uint8 xauDecimals, | ||
uint8 usdDecimals, | ||
uint80 answeredInRound | ||
) external { | ||
// Ensure the fuzzed key is within the valid range for secp256 | ||
address alice = vm.addr((fuzzedKeyAlice % (SECP256K1_ORDER - 1)) + 1); | ||
// Use common decimal bounds for price feeds | ||
// Use 0-20 so we at least have some coverage higher than 18 | ||
usdDecimals = uint8(bound(usdDecimals, 0, 20)); | ||
xauDecimals = uint8(bound(xauDecimals, 0, 20)); | ||
timestamp = bound(timestamp, 0, type(uint32).max); | ||
|
||
vm.warp(timestamp); | ||
TwoPriceOracle twoPriceOracle = createTwoPriceOracle(usdDecimals, usdDecimals, timestamp, answeredInRound); | ||
vm.startPrank(alice); | ||
// Start recording logs | ||
vm.recordLogs(); | ||
ERC20PriceOracleReceiptVault vault = createVault(address(twoPriceOracle), assetName, assetName); | ||
ReceiptContract receipt = getReceipt(); | ||
|
||
vm.mockCall(address(iAsset), abi.encodeWithSelector(IERC20.totalSupply.selector), abi.encode(1e18)); | ||
// Ensure Alice has enough balance and allowance | ||
vm.mockCall(address(iAsset), abi.encodeWithSelector(IERC20.balanceOf.selector, alice), abi.encode(assets)); | ||
|
||
uint256 totalSupply = iAsset.totalSupply(); | ||
// Getting ZeroSharesAmount if bounded from 1 | ||
assets = bound(assets, 2, totalSupply); | ||
vm.mockCall( | ||
address(iAsset), | ||
abi.encodeWithSelector(IERC20.transferFrom.selector, alice, vault, assets), | ||
abi.encode(true) | ||
); | ||
|
||
uint256 oraclePrice = twoPriceOracle.price(); | ||
|
||
vault.deposit(assets, alice, oraclePrice, bytes("")); | ||
|
||
checkNoBalanceChange( | ||
vault, alice, alice, oraclePrice, 0, receipt, abi.encodeWithSelector(ZeroAssetsAmount.selector) | ||
); | ||
} | ||
|
||
/// Test Withdraw function reverts on zero receiver | ||
function testBaseWithdrawRevertsOnZeroReceiver( | ||
uint256 fuzzedKeyAlice, | ||
string memory assetName, | ||
uint256 timestamp, | ||
uint256 assets, | ||
uint8 xauDecimals, | ||
uint8 usdDecimals, | ||
uint80 answeredInRound | ||
) external { | ||
// Ensure the fuzzed key is within the valid range for secp256 | ||
address alice = vm.addr((fuzzedKeyAlice % (SECP256K1_ORDER - 1)) + 1); | ||
// Use common decimal bounds for price feeds | ||
// Use 0-20 so we at least have some coverage higher than 18 | ||
usdDecimals = uint8(bound(usdDecimals, 0, 20)); | ||
xauDecimals = uint8(bound(xauDecimals, 0, 20)); | ||
timestamp = bound(timestamp, 0, type(uint32).max); | ||
|
||
vm.warp(timestamp); | ||
TwoPriceOracle twoPriceOracle = createTwoPriceOracle(usdDecimals, usdDecimals, timestamp, answeredInRound); | ||
vm.startPrank(alice); | ||
// Start recording logs | ||
vm.recordLogs(); | ||
ERC20PriceOracleReceiptVault vault = createVault(address(twoPriceOracle), assetName, assetName); | ||
ReceiptContract receipt = getReceipt(); | ||
|
||
vm.mockCall(address(iAsset), abi.encodeWithSelector(IERC20.totalSupply.selector), abi.encode(1e18)); | ||
// Ensure Alice has enough balance and allowance | ||
vm.mockCall(address(iAsset), abi.encodeWithSelector(IERC20.balanceOf.selector, alice), abi.encode(assets)); | ||
|
||
uint256 totalSupply = iAsset.totalSupply(); | ||
// Getting ZeroSharesAmount if bounded from 1 | ||
assets = bound(assets, 2, totalSupply); | ||
vm.mockCall( | ||
address(iAsset), | ||
abi.encodeWithSelector(IERC20.transferFrom.selector, alice, vault, assets), | ||
abi.encode(true) | ||
); | ||
|
||
uint256 oraclePrice = twoPriceOracle.price(); | ||
|
||
vault.deposit(assets, alice, oraclePrice, bytes("")); | ||
uint256 availableReceiptBalance = receipt.balanceOf(alice, oraclePrice); | ||
|
||
checkNoBalanceChange( | ||
vault, | ||
address(0), | ||
alice, | ||
oraclePrice, | ||
availableReceiptBalance, | ||
receipt, | ||
abi.encodeWithSelector(ZeroReceiver.selector) | ||
); | ||
} | ||
|
||
/// Test Withdraw function reverts on zero owner | ||
function testBaseWithdrawRevertsOnZeroOwner( | ||
uint256 fuzzedKeyAlice, | ||
string memory assetName, | ||
uint256 timestamp, | ||
uint256 assets, | ||
uint8 xauDecimals, | ||
uint8 usdDecimals, | ||
uint80 answeredInRound | ||
) external { | ||
// Ensure the fuzzed key is within the valid range for secp256 | ||
address alice = vm.addr((fuzzedKeyAlice % (SECP256K1_ORDER - 1)) + 1); | ||
// Use common decimal bounds for price feeds | ||
// Use 0-20 so we at least have some coverage higher than 18 | ||
usdDecimals = uint8(bound(usdDecimals, 0, 20)); | ||
xauDecimals = uint8(bound(xauDecimals, 0, 20)); | ||
timestamp = bound(timestamp, 0, type(uint32).max); | ||
|
||
vm.warp(timestamp); | ||
TwoPriceOracle twoPriceOracle = createTwoPriceOracle(usdDecimals, usdDecimals, timestamp, answeredInRound); | ||
vm.startPrank(alice); | ||
// Start recording logs | ||
vm.recordLogs(); | ||
ERC20PriceOracleReceiptVault vault = createVault(address(twoPriceOracle), assetName, assetName); | ||
ReceiptContract receipt = getReceipt(); | ||
|
||
vm.mockCall(address(iAsset), abi.encodeWithSelector(IERC20.totalSupply.selector), abi.encode(1e18)); | ||
// Ensure Alice has enough balance and allowance | ||
vm.mockCall(address(iAsset), abi.encodeWithSelector(IERC20.balanceOf.selector, alice), abi.encode(assets)); | ||
|
||
uint256 totalSupply = iAsset.totalSupply(); | ||
// Getting ZeroSharesAmount if bounded from 1 | ||
assets = bound(assets, 2, totalSupply); | ||
vm.mockCall( | ||
address(iAsset), | ||
abi.encodeWithSelector(IERC20.transferFrom.selector, alice, vault, assets), | ||
abi.encode(true) | ||
); | ||
|
||
uint256 oraclePrice = twoPriceOracle.price(); | ||
|
||
vault.deposit(assets, alice, oraclePrice, bytes("")); | ||
uint256 availableReceiptBalance = receipt.balanceOf(alice, oraclePrice); | ||
|
||
checkNoBalanceChange(vault, alice, address(0), oraclePrice, availableReceiptBalance, receipt, bytes("")); | ||
} | ||
|
||
/// Test PreviewWithdraw returns correct shares | ||
function testBasePreviewWithdraw( | ||
uint256 fuzzedKeyAlice, | ||
string memory assetName, | ||
uint256 timestamp, | ||
uint256 assets, | ||
uint8 xauDecimals, | ||
uint8 usdDecimals, | ||
uint80 answeredInRound | ||
) external { | ||
// Ensure the fuzzed key is within the valid range for secp256 | ||
address alice = vm.addr((fuzzedKeyAlice % (SECP256K1_ORDER - 1)) + 1); | ||
// Use common decimal bounds for price feeds | ||
// Use 0-20 so we at least have some coverage higher than 18 | ||
usdDecimals = uint8(bound(usdDecimals, 0, 20)); | ||
xauDecimals = uint8(bound(xauDecimals, 0, 20)); | ||
timestamp = bound(timestamp, 0, type(uint32).max); | ||
|
||
vm.warp(timestamp); | ||
TwoPriceOracle twoPriceOracle = createTwoPriceOracle(usdDecimals, usdDecimals, timestamp, answeredInRound); | ||
|
||
// Prank as Alice to grant role | ||
vm.startPrank(alice); | ||
ERC20PriceOracleReceiptVault vault = createVault(address(twoPriceOracle), assetName, assetName); | ||
|
||
uint256 oraclePrice = twoPriceOracle.price(); | ||
|
||
vault.setWithdrawId(oraclePrice); | ||
|
||
// Call withdraw function | ||
uint256 expectedShares = assets.fixedPointMul(oraclePrice, Math.Rounding.Up); | ||
uint256 shares = vault.previewWithdraw(assets); | ||
|
||
assertEq(shares, expectedShares); | ||
// Stop the prank | ||
vm.stopPrank(); | ||
} | ||
|
||
/// Test Withdraw function with more than assets deposied | ||
function testBaseWithdrawMoreThanAssets( | ||
uint256 fuzzedKeyAlice, | ||
string memory assetName, | ||
uint256 timestamp, | ||
uint256 assets, | ||
uint256 assetsToWithdraw, | ||
uint8 xauDecimals, | ||
uint8 usdDecimals, | ||
uint80 answeredInRound | ||
) external { | ||
// Ensure the fuzzed key is within the valid range for secp256 | ||
address alice = vm.addr((fuzzedKeyAlice % (SECP256K1_ORDER - 1)) + 1); | ||
// Use common decimal bounds for price feeds | ||
// Use 0-20 so we at least have some coverage higher than 18 | ||
usdDecimals = uint8(bound(usdDecimals, 0, 20)); | ||
xauDecimals = uint8(bound(xauDecimals, 0, 20)); | ||
timestamp = bound(timestamp, 0, type(uint32).max); | ||
|
||
vm.warp(timestamp); | ||
TwoPriceOracle twoPriceOracle = createTwoPriceOracle(usdDecimals, usdDecimals, timestamp, answeredInRound); | ||
vm.startPrank(alice); | ||
// Start recording logs | ||
vm.recordLogs(); | ||
ERC20PriceOracleReceiptVault vault = createVault(address(twoPriceOracle), assetName, assetName); | ||
ReceiptContract receipt = getReceipt(); | ||
|
||
vm.mockCall(address(iAsset), abi.encodeWithSelector(IERC20.totalSupply.selector), abi.encode(1e18)); | ||
// Ensure Alice has enough balance and allowance | ||
vm.mockCall(address(iAsset), abi.encodeWithSelector(IERC20.balanceOf.selector, alice), abi.encode(assets)); | ||
|
||
uint256 totalSupply = iAsset.totalSupply(); | ||
// Getting ZeroSharesAmount if bounded from 1 | ||
assets = bound(assets, 2, totalSupply); | ||
vm.mockCall( | ||
address(iAsset), | ||
abi.encodeWithSelector(IERC20.transferFrom.selector, alice, vault, assets), | ||
abi.encode(true) | ||
); | ||
|
||
uint256 oraclePrice = twoPriceOracle.price(); | ||
|
||
vault.deposit(assets, alice, oraclePrice, bytes("")); | ||
|
||
// Make sure assetsToWithdraw is more than assets | ||
assetsToWithdraw = bound(assetsToWithdraw, assets + 1, type(uint64).max); | ||
checkNoBalanceChange(vault, alice, alice, oraclePrice, assetsToWithdraw, receipt, bytes("")); | ||
} | ||
} |
Oops, something went wrong.