Skip to content

Commit

Permalink
Add tests for ERC20PriceOracleReceiptVault Base Withdraw
Browse files Browse the repository at this point in the history
  • Loading branch information
ninokeldishvili committed Jul 10, 2024
1 parent 9b4a706 commit b95295f
Showing 1 changed file with 368 additions and 0 deletions.
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(""));
}
}

0 comments on commit b95295f

Please sign in to comment.